Compare commits

...

224 Commits
0.3 ... 0.5

Author SHA1 Message Date
brunoherbelin
5262b8ae29 System configuration in main pannel: toggled with config button. Cleanup
the About vimix dialog, giving access to other about dialogs.
2021-01-17 23:59:25 +01:00
Bruno
1028aeea9f VERSION 0.5 2021-01-17 12:55:02 +01:00
Bruno
e02071047a Fine tuning appearance of layers view 2021-01-17 12:54:50 +01:00
brunoherbelin
227658d2fc Improved error message on different xml version. 2021-01-17 00:29:12 +01:00
brunoherbelin
56dc299fc9 Changed Mixing alpha transition function (less abrupt on the sides). 2021-01-17 00:28:45 +01:00
brunoherbelin
f7e1ff14d9 Fixed alpha blending and stippling 2021-01-16 23:37:43 +01:00
Bruno
9e865b3677 Preliminary implementation of source locking and layer stage levels 2021-01-16 22:32:02 +01:00
Bruno
e4da7de06f eye candy - changing icon 2021-01-16 15:17:11 +01:00
brunoherbelin
67f45793da Bugfix: forced DrawVisitor was not forcing actual draw() 2021-01-15 23:22:51 +01:00
brunoherbelin
5a2949609e reload view after clear new session 2021-01-13 23:46:35 +01:00
brunoherbelin
f20597656e Eye candy in texturing view 2021-01-13 23:37:02 +01:00
brunoherbelin
aaf700baba Implementation of arrow keys to move objects in views layer and
transition
2021-01-13 21:19:48 +01:00
brunoherbelin
a3e121d6a0 Fixed softwar FPS limiter when not v-sync 2021-01-13 18:38:44 +01:00
brunoherbelin
d3269e8aaa Merge commit 2021-01-13 18:38:08 +01:00
Bruno
b9104df26e Merge remote-tracking branch 'origin/master' 2021-01-13 18:23:29 +01:00
brunoherbelin
717f560326 software framerate limiter 60FPS if not v-sync 2021-01-13 18:20:55 +01:00
brunoherbelin
6fdb93a020 Support for Shift-Tab to loop backward in list of sources. 2021-01-13 14:06:54 +01:00
brunoherbelin
767b0d8084 Link zoom and grab in unified way for all views. 2021-01-12 21:49:05 +01:00
brunoherbelin
52eb9284f7 UI bugfix 2021-01-11 23:34:06 +01:00
brunoherbelin
c355486955 Improved interface mask, eye candies and new icons. 2021-01-11 23:09:52 +01:00
brunoherbelin
0e8f87d6c6 OSX compilation 2021-01-11 18:31:38 +01:00
Bruno
dbd3c071e8 Improved GUI for mask editing, added effects. 2021-01-10 23:56:50 +01:00
Bruno
398995648a Mask Paint!! New MaskShader for mouse paiting of masks and associated changes.
UI for pain mask, load & save of FrameBuffer Image.
2021-01-10 14:52:57 +01:00
brunoherbelin
3fc9401d97 Bugfix: do not forget settings for AppearanceView 2021-01-10 09:54:43 +01:00
brunoherbelin
d31320ae4b Code cleaning in screenshot 2021-01-10 09:53:55 +01:00
brunoherbelin
fe54afbe1c Cleanup and securing XMLElementEncode and DecodeArray 2021-01-10 09:52:58 +01:00
brunoherbelin
6b5ccb4450 Bugfix draw visitor (ensure clean start when visiting scene) 2021-01-10 09:51:22 +01:00
brunoherbelin
805baa75f4 Bugfix handle rotation 2021-01-02 13:59:21 +01:00
brunoherbelin
b1dd2f0bc9 Avoid unavailable pattern generators with versions of gstreamer < 18 2021-01-01 23:02:35 +01:00
brunoherbelin
f8e926040a Display transliterated filename in GUI 2021-01-01 20:41:39 +01:00
brunoherbelin
11690dfb8c ICU compilation error 2021-01-01 17:13:18 +01:00
brunoherbelin
63369223ca Compilation and snap with ICU libs 2021-01-01 15:41:31 +01:00
brunoherbelin
a1e81b58b1 OSX compilation fix 2021-01-01 14:36:44 +01:00
brunoherbelin
cf2b6b4b39 Improved information icon for source preview 2021-01-01 12:17:49 +01:00
brunoherbelin
c4e584a1da cleanup framebuffer info string 2021-01-01 12:00:30 +01:00
brunoherbelin
25b58b76f3 Enable resize of session frame buffer resolution 2021-01-01 11:54:40 +01:00
Bruno
b346403887 Try to use GPU video decoding plugins when possible 2021-01-01 10:09:17 +01:00
Bruno
e0cd560dfb Implementation of 2 corner masks 2020-12-31 18:27:33 +01:00
brunoherbelin
4313e51530 Minor GUI bugfix 2020-12-31 10:51:41 +01:00
brunoherbelin
e2bb90208e Bugs fixed and eye candies 2020-12-31 00:50:50 +01:00
Bruno
85d72a1c0e Transliteration of source name 2020-12-30 17:23:31 +01:00
Bruno
a073ab41dd Improved procedural masks 2020-12-27 21:43:33 +01:00
Bruno
34c24d99df Integration procedural GLSL masks 2020-12-27 14:05:03 +01:00
Bruno
69d9d9473b Implemented procedural GLSL masks
Create MaskShader and added manipulation of masks in Appearance view
2020-12-27 14:04:23 +01:00
brunoherbelin
a58c06c617 Fixed display cropped source in AppearanceView and code cleanup. 2020-12-08 23:43:50 +01:00
brunoherbelin
b7a54d0512 BugFix deep update views (depth and layout) and crop. 2020-12-08 23:04:12 +01:00
brunoherbelin
1677582034 Bug fix on deep update of views and scenes: incrementing a counter of
updates needed (instead of only a flag).
2020-12-07 23:39:41 +01:00
brunoherbelin
44b888fd04 Work in progress - implementation of cropping in Geometry view instead
of AppearanceView. Display of scaled mixing surface in Mixing and Layers
view. Changed stippling shading.
2020-12-07 00:17:10 +01:00
brunoherbelin
78f9216d32 Hiding grips in geometry manipulation when operating. 2020-12-05 00:22:46 +01:00
brunoherbelin
1ea0ec53af Minor fixed GUI and mouse cursor 2020-12-03 23:21:30 +01:00
brunoherbelin
688823e63f New doc images for Appearance view 2020-12-01 23:37:52 +01:00
BHBN
78f4b80b84 Merge pull request #13 from brunoherbelin/dev
Dev
2020-12-01 21:58:28 +01:00
brunoherbelin
bb8f536f0a BugFix user interface 2020-12-01 21:28:37 +01:00
brunoherbelin
ca51c5348e keep iTransform and color when replacing shader 2020-11-30 23:16:58 +01:00
brunoherbelin
cc4d9ede97 fixed activation of current source in selection when entering
AppearanceView
2020-11-30 23:04:38 +01:00
brunoherbelin
053c2a3f1f Cosmetics in views and GUI 2020-11-30 22:59:15 +01:00
brunoherbelin
1538e7b85b Cleanup display AppearanceView 2020-11-30 22:21:06 +01:00
brunoherbelin
61d2a4dcb9 Cleanup GUI and user feedback with crop in AppearanceView. 2020-11-30 21:39:41 +01:00
brunoherbelin
3c55e25432 Finally a working implementation of crop and UV manipulation in
AppearanceView! Added saving and loading.
2020-11-30 00:25:02 +01:00
brunoherbelin
93ad971fc0 Improved implementation of crop in AppearanceView. Not yet fully
satisfying though...
2020-11-29 23:33:53 +01:00
brunoherbelin
5200de2e3e Implementation of image transformation in shaders: iTransform is a
generic UV coordinates transformation matrix (for translation, scaling
and rotation) edited in AppearanceView. Removing previous UV editing
functions.
2020-11-29 12:31:06 +01:00
brunoherbelin
d92736b38f cleanup 2020-11-26 22:20:11 +01:00
brunoherbelin
20f1320e2d Implementation of vertical crop (2 axis projection manipulation in frame
buffer). Cleanup of UI for appearance view.
2020-11-26 20:30:37 +01:00
brunoherbelin
b6af17f283 Add soft shadow behind icons decoration to improve visibility in white
background.
2020-11-24 19:22:41 +01:00
brunoherbelin
4a6a110e3d Fixed behavior for current and edit source selection in AppearanceView. 2020-11-23 23:35:48 +01:00
brunoherbelin
7f161a0a49 Add Reference to original scale of image in AppearanceView (in
preparation to crop)
2020-11-23 00:04:03 +01:00
brunoherbelin
30301b51d4 Soft shadow behind frame handles to make them more visible in contrast. 2020-11-23 00:03:21 +01:00
brunoherbelin
c33796e97c Beta implementation of FrameBuffer projection change for cropping. 2020-11-22 21:58:04 +01:00
brunoherbelin
61ee23455b update source after action on view context menu 2020-11-22 09:16:35 +01:00
brunoherbelin
1cd36b6134 bugfix too much updating when set currentSource 2020-11-21 23:05:20 +01:00
brunoherbelin
59087f9198 Fixed current source selection behavior with swich to appearance view. 2020-11-21 21:29:08 +01:00
brunoherbelin
bb231868b4 Bugfix in UV Apprearance update. Fixed Context menu. 2020-11-18 23:16:38 +01:00
brunoherbelin
c841c0e342 Change MediaSource timeline curve to make darker instead of transparent. 2020-11-18 23:16:01 +01:00
brunoherbelin
4630d39663 New context menu in Geometry and Appearance Views: special handle (upper
left corner) with new icon and view-specific context menu on current
source.
2020-11-17 23:28:11 +01:00
brunoherbelin
3b529222d8 Completing the user actions in appearance view. 2020-11-16 23:47:02 +01:00
brunoherbelin
1ab2ae0df0 Operational implementation of UV texture coordinates changes in
Appearance View.
2020-11-16 21:43:39 +01:00
brunoherbelin
8c9b753544 First operational version of AppearanceView. Cleanup of symbols for
sources and other bugfix.
2020-11-15 23:49:56 +01:00
brunoherbelin
196ce3df1b Cleanup of surface management in Sources (centralize texturesurface_ in
Source class, avoid mistakes in subclasses). Integration of sources in
AppearanceView (not functionnal yet).
2020-11-15 13:01:06 +01:00
brunoherbelin
15a0bab925 Creating view for APPEARANCE control 2020-11-13 22:35:32 +01:00
brunoherbelin
95ed47934d making snap access /dev/video 2020-11-13 22:34:48 +01:00
brunoherbelin
8c2c2302d1 making snap access /dev/video* 2020-11-13 22:31:22 +01:00
brunoherbelin
187a6132fc fixed loop EOS 2020-11-12 22:39:14 +01:00
brunoherbelin
b3fd29056e saving uv in mix session file 2020-11-12 22:11:52 +01:00
brunoherbelin
11a58b5adf Adding UV texture coordinates to Surface and ImageShader. 2020-11-12 22:07:00 +01:00
brunoherbelin
7c5374552d set unique surface_ pointer for all source types 2020-11-12 18:38:51 +01:00
brunoherbelin
d33ff427b5 Fix single frame image (e.g. GIF) 2020-11-12 18:09:14 +01:00
brunoherbelin
32a4607673 Fixed loading and playing of GIF animation 2020-11-11 19:38:57 +01:00
brunoherbelin
6f37ca8a84 more documentation pics 2020-11-10 23:39:32 +01:00
brunoherbelin
f9dcd7348e Force stable fps for v4l2loopback (crashing drivers otherwise) 2020-11-10 22:58:05 +01:00
brunoherbelin
f4baa67089 Cleanup dialog v4l2loopback 2020-11-10 22:16:14 +01:00
brunoherbelin
39f8b56c99 Simplified v4l2loopback system 2020-11-10 20:27:03 +01:00
brunoherbelin
ea1192c334 bugfix 2020-11-10 11:02:04 +01:00
brunoherbelin
56dfbc737d Deep redesign of frame grabbers to prevent multiple frame captures
(slow) and optimize distribution of gabbed frames to multiple recording
and streaming pipelines (e.g. record H264 *and* share stream on the
network). New implementation of Loopback source for LINUX using
v4l2loopback (experimental).
2020-11-09 23:56:41 +01:00
brunoherbelin
1c1a0e9b33 Using system wide temp directory for shared memory socket 9for linux
snaps to be able to share).
2020-11-07 18:54:33 +01:00
brunoherbelin
fcc014e5d1 compilation warning fix 2020-11-07 18:36:52 +01:00
brunoherbelin
bfb0576e26 Attempt to fix vsync problem (tearing frames) under linux. 2020-11-07 18:36:34 +01:00
brunoherbelin
71140a8c6c Documenting source creation 2020-11-07 14:49:01 +01:00
brunoherbelin
6d80c798f5 Implemented Timers in Metrics toolkit 2020-11-06 23:03:20 +01:00
brunoherbelin
d3f6f2f87d switch to v0.4 confirmed 2020-11-06 19:25:35 +01:00
brunoherbelin
d1050e9fdf Merge branch 'master' of github.com:brunoherbelin/vimix 2020-11-06 19:16:49 +01:00
brunoherbelin
105294fdaf Document video share feature 2020-11-06 19:16:25 +01:00
brunoherbelin
4755f47286 Change the way to include gst-plugins-bad in OSX package 2020-11-06 19:04:43 +01:00
brunoherbelin
77da91efa5 Fix OSX cpack 2020-11-04 23:06:47 +01:00
brunoherbelin
e679f18d93 Fix OSX compilation GST Bad plugins 2020-11-04 22:47:43 +01:00
brunoherbelin
c2531cf035 BugFix stop stream without deleting it 2020-11-04 22:23:37 +01:00
brunoherbelin
2124dfc718 BugFix update frame in stream after reset 2020-11-04 22:22:48 +01:00
brunoherbelin
ce5369a0ef Default settings to not accept connections 2020-11-03 22:31:40 +01:00
brunoherbelin
ec797f8d67 Various GUI terminology changes for unified wording and clarity 2020-11-03 21:52:03 +01:00
brunoherbelin
ce7f30fa63 Minor GUI improvement connection 2020-11-03 19:13:20 +01:00
brunoherbelin
79482d3d1b Offer to reconnect a network source at anytime (there is no way to
really know if it was disconnected)
2020-11-03 18:44:12 +01:00
brunoherbelin
93e7027f48 Fixed Connection and Streamer mechanisms 2020-11-03 18:34:38 +01:00
brunoherbelin
34580ab5ea Fixup OSX system session file loading request 2020-11-03 17:56:18 +01:00
brunoherbelin
bab0e9b710 OSX support for 'OpenFile' system message (aka open vimix when selecting
session file in finder).
2020-11-02 20:55:38 +01:00
brunoherbelin
88d4e3d9d5 Added support for filename argument when running vimix (open session) 2020-11-01 23:59:24 +01:00
brunoherbelin
47c338341d Fix exit crash 2020-11-01 23:49:36 +01:00
brunoherbelin
3cae0cd66f Improved memory management Stream 2020-11-01 23:33:28 +01:00
brunoherbelin
0738c25fb4 Fix memory leak stream UDP 2020-11-01 23:32:40 +01:00
brunoherbelin
b8ebab5766 Fixing Streamer and NetworkSource dialog 2020-11-01 18:13:37 +01:00
brunoherbelin
954b35032a Fix Connection broadcaster 2020-11-01 15:11:26 +01:00
brunoherbelin
46b9a8f663 Fixed fullscreen main window for OSX 2020-11-01 13:18:49 +01:00
brunoherbelin
41f87aa927 Fix OSX fullscreen crash 2020-11-01 11:00:46 +01:00
brunoherbelin
05a4ac164e Merge branch 'master' of github.com:brunoherbelin/vimix 2020-10-31 19:23:47 +01:00
brunoherbelin
44901b1e78 various minor OSX compilation update 2020-10-31 19:21:21 +01:00
brunoherbelin
8ef79a6dbd Added frame buffer information display in session preview 2020-10-31 19:21:05 +01:00
brunoherbelin
940dd0f2a5 Using OSX avenc hardware decoder 2020-10-31 19:19:34 +01:00
brunoherbelin
4fa7e06e19 oops missing include 2020-10-26 21:43:33 +01:00
brunoherbelin
7f2c3d531c OSX compatibility posix for NetworkToolkit 2020-10-26 21:40:21 +01:00
brunoherbelin
a4621f31e3 OSX compilation compatibility 2020-10-26 19:32:19 +01:00
brunoherbelin
7438b257ae Added icon for NetworkSource (sharing logo) 2020-10-25 23:26:04 +01:00
brunoherbelin
cb6a0aefa4 Minor improvements in connection and IPC (multiple instances not fully
supported yet)
2020-10-25 23:14:47 +01:00
brunoherbelin
7fba62bc49 minor rename 2020-10-25 22:03:49 +01:00
brunoherbelin
01410a59cf Improved connection robustness and diconnection/connection behaviors
(added connection rejection to streamer).
2020-10-25 22:01:04 +01:00
brunoherbelin
e60c7a4cad Managing client name and disconnection (e.g. end vimix) 2020-10-25 18:56:56 +01:00
brunoherbelin
8fa14bda1a Fixing bugs with Network source ans Video Streamer. 2020-10-25 14:31:27 +01:00
brunoherbelin
469ee4c26a Finalizing NetworkSource (Visitors) 2020-10-25 10:00:32 +01:00
brunoherbelin
2627174fc0 To confirm: working implementation of SHM and UDP streaming connection 2020-10-25 00:23:44 +02:00
brunoherbelin
7246dfa08e Work-in progress: connection manager now used in Streamer and
NetworkSource to identify possible connections, and exchange streaming
configuration.
2020-10-24 23:53:11 +02:00
brunoherbelin
db0892d25b Defining a name for a Connection 2020-10-23 21:54:45 +02:00
brunoherbelin
509416d5a0 Connection manager seems to work... 2020-10-23 19:01:44 +02:00
brunoherbelin
43f444f07b Creation of the Connection Manager : this new mechanism continuously
checks for the presence of vimix programs in the network neibourhood.
The list of connections can then be used for indentifying streaming
requests and offers.
2020-10-23 01:02:28 +02:00
brunoherbelin
bbeb99056a Update to OSCPack v1.1 2020-10-20 18:27:26 +02:00
brunoherbelin
65aefc9fb8 Complete integration of original OSCPack lib 2020-10-20 18:18:44 +02:00
brunoherbelin
27239b7513 working on streaming and clients 2020-10-20 00:28:44 +02:00
brunoherbelin
15285ec151 Added lib OSCPack 2020-10-20 00:28:16 +02:00
brunoherbelin
d7893be541 First working implementation of Streamer, with TCP and SharedMemory. 2020-10-18 13:13:07 +02:00
brunoherbelin
59c07ceb96 First working implementation of VideoStreamer 2020-10-17 11:32:29 +02:00
brunoherbelin
007d876dbc Initial commit of Video Streamer. bugFix delete Pbos. 2020-10-14 22:58:02 +02:00
brunoherbelin
3a41e59f00 Management of recorders by id in user interface. 2020-10-14 22:37:53 +02:00
brunoherbelin
3a34da9322 Renaming Recorder to FrameGrabber 2020-10-14 21:04:22 +02:00
brunoherbelin
b3ee400b1a Hack to prevent re-openning automatically a session file in case vimix
was not properly closed (to avoid crash at start that prevent vimix from
restarting after loading a faulty session file).
2020-10-13 23:42:33 +02:00
brunoherbelin
102413c7f4 Minor compilation fixes 2020-10-12 16:41:34 +02:00
brunoherbelin
c674fa0897 Implementation of Copy, Cut & Paste of sources. Keeps description (xml)
of source in clipboard for pasting in another session or in the same
(then it clones existing sources).
2020-10-11 23:41:24 +02:00
brunoherbelin
bd922f5bcc Improved Esc key and Ctrl+Q behavior 2020-10-11 16:54:30 +02:00
brunoherbelin
1390eff646 Action manager follows View of restored action (user settings) 2020-10-11 16:02:41 +02:00
brunoherbelin
34b508a8dd BugFix: PatternSource pattern type is unsigned and undefined when
created
2020-10-11 16:01:57 +02:00
brunoherbelin
8297c85220 Cosmetic improvement of Action manager and Log messages. 2020-10-11 12:05:38 +02:00
brunoherbelin
795c0ed30f BugFix: ensure update of source after update by SessionLoader 2020-10-11 12:04:58 +02:00
brunoherbelin
babbddcf28 Improved Action manager labels of stored actions 2020-10-11 00:37:49 +02:00
brunoherbelin
650017c8f3 BugFix: history manager undo and redo for delete and recreation of
source, delete multiple sources or clones, etc.
2020-10-11 00:37:04 +02:00
brunoherbelin
2c1eaff476 improvement in order of delete in selection (for better history of
delete)
2020-10-10 15:30:28 +02:00
brunoherbelin
c0e135993c BugFix: prevent visitors for failed sources. Avoid duplicate list of
source ids.
2020-10-10 15:16:47 +02:00
brunoherbelin
22011ffd54 Avoid extension specific opengl calls (not always working...) 2020-10-10 12:02:09 +02:00
brunoherbelin
31ebccd248 Hack to prevent font oversampling for low-memory graphics card. 2020-10-10 10:48:39 +02:00
brunoherbelin
af11408ee9 Heuristics to prevent font displaying issues. 2020-10-10 10:29:11 +02:00
brunoherbelin
99f5236959 Compile issues for 64bits int under i386 2020-10-08 20:26:17 +02:00
brunoherbelin
67463d2214 Prevent erroneous font size 2020-10-07 22:04:20 +02:00
brunoherbelin
3aabb83ccf BugFix: remove session filename from list or recent files if failed
loading.
2020-10-07 20:18:11 +02:00
brunoherbelin
82b755db84 Merge branch 'master' of github.com:brunoherbelin/vimix 2020-10-07 19:50:09 +02:00
brunoherbelin
10dc426119 Fixed compilation issue (OSX) 2020-10-07 12:07:03 +02:00
brunoherbelin
233fc64c4e Increased size of unique identifier of objects: using uint64 instead of
int. Deep change in all concerned objects (Node, Source, Shader, etc.).
No behavior change, just more robust in duration.
2020-10-06 22:45:52 +02:00
brunoherbelin
977ae76f9b Eye candy; rounding up the font size in monitor. 2020-10-06 21:40:16 +02:00
brunoherbelin
77d9b17ac8 HistoryManager: new widget and extending scope of historized user
actions.
2020-10-06 21:39:37 +02:00
brunoherbelin
6f4b75ec1c Cleanup z compression XML array 2020-10-06 09:11:16 +02:00
brunoherbelin
2faa499ace Z-compression of the timeline fading when saving to XML; data on disk
are much faster to load, and files smaller.
2020-10-06 00:03:26 +02:00
brunoherbelin
2493d8d9f9 Added ImGUI actions to ActionManager history. 2020-10-04 23:34:59 +02:00
brunoherbelin
616c6c8bdf First working implementation of ActionManager, but incomplete. 2020-10-04 22:52:23 +02:00
brunoherbelin
5421b5e926 Cleanup Session loading (prepare for session history) 2020-10-03 18:45:30 +02:00
brunoherbelin
d563ee14a9 Cleanup source id management, and improved session merging and source
renaming.
2020-10-03 17:54:34 +02:00
brunoherbelin
3e5b1e74e8 Adding ActionManager 2020-10-03 14:05:58 +02:00
brunoherbelin
f32b85a656 Centralizing and unifying the generator of object identifiers. 2020-10-03 14:05:28 +02:00
brunoherbelin
61e5c046c0 possible implementation of a confirmation action when deleting source 2020-10-02 18:24:51 +02:00
brunoherbelin
0eaffe213a Changed order of filter sliders, and publish related documentation 2020-10-02 13:24:39 +02:00
brunoherbelin
b2a316b813 Updated description (link to user manual) 2020-10-02 12:06:31 +02:00
brunoherbelin
fac798df93 BugFix: prevent from creating twice a source with the same device:
create a clone of the original device source instead.
2020-10-01 23:44:14 +02:00
brunoherbelin
83a2da6b2b Setup icon (i,j coordinates in ImGui Toolkit) for each Source class, and
use this icon in GUI to indicate the type of class.
2020-10-01 22:57:49 +02:00
brunoherbelin
467ed23b37 Optimizing size of icon file 2020-10-01 22:11:56 +02:00
brunoherbelin
41e0a6f0be updtate gitignore 2020-09-30 23:40:41 +02:00
brunoherbelin
00ebacc9db Clean source icons 2020-09-30 23:38:38 +02:00
brunoherbelin
e0d44d4db1 LICENSE for icons 2020-09-30 23:23:19 +02:00
brunoherbelin
e8a88fcbb9 Improve icons 2020-09-30 23:21:43 +02:00
brunoherbelin
d4b014188e Fixed DeviceSource pannel config 2020-09-30 22:32:29 +02:00
brunoherbelin
b05207b27d Implementation of Device source for OSX. Improved support for various
frame formats and fps
2020-09-30 20:32:05 +02:00
brunoherbelin
c777a3d153 work in progress: implementation of stash in MixingView 2020-09-30 12:01:40 +02:00
brunoherbelin
1bada746dc Increase snap version to 0.3 (matching vimix internal version) 2020-09-28 23:04:55 +02:00
brunoherbelin
0d53afb6fd Fixed order for new patterns 2020-09-28 23:00:15 +02:00
brunoherbelin
3b31d33c90 Improved zoom slider scaling and display 2020-09-28 22:59:53 +02:00
brunoherbelin
dcffb1cbaa Change 'error' message for canceled (not failed) loading of session 2020-09-27 23:57:47 +02:00
brunoherbelin
bbd105983d Playing with patterns 2020-09-27 23:57:15 +02:00
brunoherbelin
1d7e0838fa New UI for scaling (zoom) view from view pannel icon. 2020-09-27 21:04:40 +02:00
brunoherbelin
d9a205d9ab More images for manual 2020-09-26 21:28:10 +02:00
brunoherbelin
39ceea9690 Remove debug info 2020-09-26 20:02:36 +02:00
brunoherbelin
89891a18e5 Minor bug fix PatternSource. 2020-09-26 20:02:17 +02:00
brunoherbelin
2b59a0e6ed Added images for wiki 2020-09-26 19:38:56 +02:00
brunoherbelin
688aee8831 Fixed resolution issue with different monitor DPI. 2020-09-26 14:25:11 +02:00
brunoherbelin
047163a38c Merge branch 'master' of github.com:brunoherbelin/vimix into testing_gst 2020-09-26 12:31:47 +02:00
brunoherbelin
df2a66484b First minimal implementation of Screen device for screen capture under
Linux (ximagesrc). Cleanup code.
2020-09-26 12:22:15 +02:00
brunoherbelin
69c74aa103 Save & Load Device Source. 2020-09-25 22:03:31 +02:00
brunoherbelin
a4ff2a325f Improved detection of unplugged device 2020-09-25 21:44:38 +02:00
brunoherbelin
7109b94484 Cleanup DeviceSource and PatternSource (no feature change) 2020-09-25 21:20:24 +02:00
brunoherbelin
202db9eaa2 First draft of an automatic discoverer for gst device source caps
(framerate, resolution and image format) to enable auto creation of
DeviceSource.
2020-09-25 00:05:35 +02:00
brunoherbelin
84caf2da9a Integrated preliminary implementation of Device class with monitoring of
v4l2 device connected to the machine using GstDeviceMonitor.
2020-09-23 23:29:45 +02:00
brunoherbelin
9e160fec51 Fixed Device source by enabling/disabling the sync option of GstAppSink
depending on the live mode of the pipeline (detected at pipeline first
status change). Cleanup and unified info debug messages of all stream
sources.
2020-09-23 17:28:45 +02:00
brunoherbelin
b7d54dfadf Creation of the base class StreamSource. It is intended to become the
base class for all gstreamer pipeline. DeviceSource and PatternSource
inherit from it. A generic stream source class is created for
development tests and hack.
2020-09-22 22:58:37 +02:00
brunoherbelin
04e03456bf Updated OSX gst-bad plugin to 18.0 for install. 2020-09-21 23:25:29 +02:00
brunoherbelin
e84b16c9ce Unfortunately some frei0r patterns are not available under OSX; removing
them for the default patterns.
2020-09-21 23:01:54 +02:00
brunoherbelin
9251aff19f Create Device Source and integration of Stream 2020-09-21 22:41:20 +02:00
brunoherbelin
519baf7a3b Unified GUI behavior for source creation. cleanup of history of recent
files after loading error.
2020-09-20 11:28:09 +02:00
brunoherbelin
59db2cf57c Implementation of new type of source: Pattern generator (GUI, icons,
saving). A new class for gstreamer stream (Stream) is defined for
generic pipeline sources; to be integrated (inheritance) into
MediaPlayer.
2020-09-20 00:26:39 +02:00
brunoherbelin
db6d3a6fa0 Support for 21:9 aspect ratio 2020-09-19 20:45:16 +02:00
brunoherbelin
1209d337bc Updated webpage image + link to wiki 2020-09-19 01:04:05 +02:00
brunoherbelin
b0e54c6ff5 Increased range for UI scaling 2020-09-16 22:04:30 +02:00
brunoherbelin
76f067de55 Merge branch 'master' of github.com:brunoherbelin/vimix 2020-09-16 21:36:32 +02:00
brunoherbelin
9e32e4f5b2 Improved user feedback on session loading 2020-09-16 08:34:53 +02:00
brunoherbelin
76926f433c Also draw frames of all sources in GeometryView 2020-09-15 20:19:10 +02:00
204 changed files with 24386 additions and 2610 deletions

4
.gitignore vendored
View File

@@ -14,3 +14,7 @@ libTINYXML2.a
libvmix-resources.a
rules.ninja
/vmix
/vimix_*.snap
/CMakeLists.txt.user.*
rsc/shaders/paint.fs

255
ActionManager.cpp Normal file
View File

@@ -0,0 +1,255 @@
#include <string>
#include <algorithm>
#include "Log.h"
#include "View.h"
#include "Mixer.h"
#include "tinyxml2Toolkit.h"
#include "SessionVisitor.h"
#include "SessionCreator.h"
#include "Settings.h"
#include "ActionManager.h"
#ifndef NDEBUG
#define ACTION_DEBUG
#endif
using namespace tinyxml2;
Action::Action(): step_(0), max_step_(0)
{
}
void Action::clear()
{
// clean the history
xmlDoc_.Clear();
step_ = 0;
max_step_ = 0;
// start fresh
store("Session start");
}
void Action::store(const std::string &label, uint64_t id)
{
// ignore if locked or if no label is given
if (locked_ || label.empty())
return;
// incremental naming of history nodes
step_++;
std::string nodename = "H" + std::to_string(step_);
// erase future
for (uint e = step_; e <= max_step_; e++) {
std::string name = "H" + std::to_string(e);
XMLElement *node = xmlDoc_.FirstChildElement( name.c_str() );
if ( node )
xmlDoc_.DeleteChild(node);
}
max_step_ = step_;
// create history node
XMLElement *sessionNode = xmlDoc_.NewElement( nodename.c_str() );
xmlDoc_.InsertEndChild(sessionNode);
// label describes the action
sessionNode->SetAttribute("label", label.c_str());
// id indicates which object was modified
sessionNode->SetAttribute("id", id);
// view indicates the view when this action occured
sessionNode->SetAttribute("view", (int) Mixer::manager().view()->mode());
// get session to operate on
Session *se = Mixer::manager().session();
// save all sources using source visitor
SessionVisitor sv(&xmlDoc_, sessionNode);
for (auto iter = se->begin(); iter != se->end(); iter++, sv.setRoot(sessionNode) )
(*iter)->accept(sv);
// debug
#ifdef ACTION_DEBUG
Log::Info("Action stored %s '%s'", nodename.c_str(), label.c_str());
// XMLSaveDoc(&xmlDoc_, "/home/bhbn/history.xml");
#endif
}
void Action::undo()
{
// not possible to go to 1 -1 = 0
if (step_ <= 1)
return;
// what id was modified to get to this step ?
// get history node of current step
std::string nodename = "H" + std::to_string(step_);
XMLElement *sessionNode = xmlDoc_.FirstChildElement( nodename.c_str() );
uint64_t id = 0;
sessionNode->QueryUnsigned64Attribute("id", &id);
// restore always changes step_ to step_ - 1
restore( step_ - 1, id);
}
void Action::redo()
{
// not possible to go to max_step_ + 1
if (step_ >= max_step_)
return;
// what id to modify to go to next step ?
std::string nodename = "H" + std::to_string(step_ + 1);
XMLElement *sessionNode = xmlDoc_.FirstChildElement( nodename.c_str() );
uint64_t id = 0;
sessionNode->QueryUnsigned64Attribute("id", &id);
// restore always changes step_ to step_ + 1
restore( step_ + 1, id);
}
void Action::stepTo(uint target)
{
// get reasonable target
uint t = CLAMP(target, 1, max_step_);
// going backward
if ( t < step_ ) {
// go back one step at a time
while (t < step_)
undo();
}
// step forward
else if ( t > step_ ) {
// go forward one step at a time
while (t > step_)
redo();
}
// ignore t == step_
}
std::string Action::label(uint s) const
{
std::string l = "";
if (s > 0 && s <= max_step_) {
std::string nodename = "H" + std::to_string(s);
const XMLElement *sessionNode = xmlDoc_.FirstChildElement( nodename.c_str() );
l = sessionNode->Attribute("label");
}
return l;
}
void Action::restore(uint target, uint64_t id)
{
// lock
locked_ = true;
// get history node of target step
step_ = CLAMP(target, 1, max_step_);
std::string nodename = "H" + std::to_string(step_);
XMLElement *sessionNode = xmlDoc_.FirstChildElement( nodename.c_str() );
// ask view to refresh, and switch to action view if user prefers
int view = Settings::application.current_view ;
if (Settings::application.action_history_follow_view)
sessionNode->QueryIntAttribute("view", &view);
Mixer::manager().setView( (View::Mode) view);
#ifdef ACTION_DEBUG
Log::Info("Restore %s '%s' ", nodename.c_str(), sessionNode->Attribute("label"));
#endif
// we operate on the current session
Session *se = Mixer::manager().session();
if (se == nullptr)
return;
// sessionsources contains list of ids of all sources currently in the session
std::list<uint64_t> sessionsources = se->getIdList();
// for( auto it = sessionsources.begin(); it != sessionsources.end(); it++)
// Log::Info("sessionsources id %s", std::to_string(*it).c_str());
// load history status:
// - if a source exists, its attributes are updated, and that's all
// - if a source does not exists (in session), it is created in the session
SessionLoader loader( se );
loader.load( sessionNode );
// loadersources contains list of ids of all sources generated by loader
std::list<uint64_t> loadersources = loader.getIdList();
// for( auto it = loadersources.begin(); it != loadersources.end(); it++)
// Log::Info("loadersources id %s", std::to_string(*it).c_str());
// remove intersect of both lists (sources were updated by SessionLoader)
for( auto lsit = loadersources.begin(); lsit != loadersources.end(); ){
auto ssit = std::find(sessionsources.begin(), sessionsources.end(), (*lsit));
if ( ssit != sessionsources.end() ) {
lsit = loadersources.erase(lsit);
sessionsources.erase(ssit);
}
else
lsit++;
}
// remaining ids in list sessionsources : to remove
while ( !sessionsources.empty() ){
Source *s = Mixer::manager().findSource( sessionsources.front() );
if (s!=nullptr) {
#ifdef ACTION_DEBUG
Log::Info("Delete id %s", std::to_string(sessionsources.front() ).c_str());
#endif
// remove the source from the mixer
Mixer::manager().detach( s );
// delete source from session
se->deleteSource( s );
}
sessionsources.pop_front();
}
// remaining ids in list loadersources : to add
while ( !loadersources.empty() ){
#ifdef ACTION_DEBUG
Log::Info("Recreate id %s to %s", std::to_string(id).c_str(), std::to_string(loadersources.front()).c_str());
#endif
// change the history to match the new id
replaceSourceId(id, loadersources.front());
// add the source to the mixer
Mixer::manager().attach( Mixer::manager().findSource( loadersources.front() ) );
loadersources.pop_front();
}
// free
locked_ = false;
}
void Action::replaceSourceId(uint64_t previousid, uint64_t newid)
{
// loop over every session history step
XMLElement* historyNode = xmlDoc_.FirstChildElement("H1");
for( ; historyNode ; historyNode = historyNode->NextSiblingElement())
{
// check if this history node references this id
uint64_t id_history_ = 0;
historyNode->QueryUnsigned64Attribute("id", &id_history_);
if ( id_history_ == previousid )
// change to new id
historyNode->SetAttribute("id", newid);
// loop over every source in session history
XMLElement* sourceNode = historyNode->FirstChildElement("Source");
for( ; sourceNode ; sourceNode = sourceNode->NextSiblingElement())
{
// check if this source node has this id
uint64_t id_source_ = 0;
sourceNode->QueryUnsigned64Attribute("id", &id_source_);
if ( id_source_ == previousid )
// change to new id
sourceNode->SetAttribute("id", newid);
}
}
}

48
ActionManager.h Normal file
View File

@@ -0,0 +1,48 @@
#ifndef ACTIONMANAGER_H
#define ACTIONMANAGER_H
#include <atomic>
#include <tinyxml2.h>
class Action
{
// Private Constructor
Action();
Action(Action const& copy); // Not Implemented
Action& operator=(Action const& copy); // Not Implemented
public:
static Action& manager()
{
// The only instance
static Action _instance;
return _instance;
}
void store(const std::string &label, uint64_t id = 0);
void clear();
void undo();
void redo();
void stepTo(uint target);
inline uint current() const { return step_; }
inline uint max() const { return max_step_; }
std::string label(uint s) const;
private:
void restore(uint target, uint64_t id);
void replaceSourceId(uint64_t previousid, uint64_t newid);
tinyxml2::XMLDocument xmlDoc_;
uint step_;
uint max_step_;
std::atomic<bool> locked_;
};
#endif // ACTIONMANAGER_H

View File

@@ -13,10 +13,10 @@ if(UNIX)
# the RPATH to be used when installing
set(CMAKE_SKIP_RPATH TRUE)
set(OpenGL_DIR /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/)
set(CMAKE_OSX_ARCHITECTURES x86_64)
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13")
# set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13" CACHE STRING "Minimum OS X version to target for deployment")
else()
add_definitions(-DLINUX)
@@ -91,6 +91,10 @@ set(THREAD_LIBRARY Threads::Threads)
find_package(PNG REQUIRED)
set(PNG_LIBRARY PNG::PNG)
find_package(ICU REQUIRED COMPONENTS i18n io uc)
macro_log_feature(ICU_FOUND "ICU" "International Components for Unicode" "http://site.icu-project.org" TRUE)
#
# GLFW3
# NB: set glfw3_PATH to /usr/local/Cellar/glfw/3.3.2/lib/cmake/glfw3
@@ -149,6 +153,28 @@ set(TINYXML2_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/tinyxml2)
add_library(TINYXML2 "${CMAKE_CURRENT_SOURCE_DIR}/ext/tinyxml2/tinyxml2.cpp")
message(STATUS "Compiling 'TinyXML2' from https://github.com/leethomason/tinyxml2.git -- ${TINYXML2_INCLUDE_DIR}.")
#
# OSCPack
#
if(UNIX)
set(OSCPACK_PLATFORM_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/OSCPack/ip/posix/)
elseif(WIN32)
set(OSCPACK_PLATFORM_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/OSCPack/ip/win32/)
endif()
set(OSCPACK_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/ext/OSCPack/osc/OscTypes.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ext/OSCPack/osc/OscReceivedElements.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ext/OSCPack/osc/OscPrintReceivedElements.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ext/OSCPack/osc/OscOutboundPacketStream.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ext/OSCPack/ip/IpEndpointName.cpp
${OSCPACK_PLATFORM_DIR}/NetworkingUtils.cpp
${OSCPACK_PLATFORM_DIR}/UdpSocket.cpp
)
set(OSCPACK_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/OSCPack)
add_library(OSCPACK "${OSCPACK_SRCS}")
message(STATUS "Compiling 'OSCPack' from http://www.rossbencina.com/code/oscpack -- ${OSCPACK_INCLUDE_DIR}.")
#
# STB
#
@@ -204,7 +230,8 @@ include_directories(
${TINYFD_INCLUDE_DIR}
${STB_INCLUDE_DIR}
${DIRENT_INCLUDE_DIR}
${OBJLOADER_INCLUDE_DIR}
${OSCPACK_INCLUDE_DIR}
${ICU_INCLUDE_DIRS}
)
@@ -229,14 +256,22 @@ set(VMIX_SRCS
GarbageVisitor.cpp
SessionCreator.cpp
Mixer.cpp
FrameGrabber.cpp
Recorder.cpp
Streamer.cpp
Loopback.cpp
Settings.cpp
Screenshot.cpp
Resource.cpp
FileDialog.cpp
Timeline.cpp
Stream.cpp
MediaPlayer.cpp
MediaSource.cpp
StreamSource.cpp
PatternSource.cpp
DeviceSource.cpp
NetworkSource.cpp
FrameBuffer.cpp
RenderingManager.cpp
UserInterfaceManager.cpp
@@ -250,12 +285,22 @@ set(VMIX_SRCS
GlmToolkit.cpp
SystemToolkit.cpp
tinyxml2Toolkit.cpp
NetworkToolkit.cpp
Connection.cpp
ActionManager.cpp
)
set(VMIX_RSC_FILES
./rsc/shaders/simple.fs
./rsc/shaders/simple.vs
./rsc/shaders/image.fs
./rsc/shaders/mask_elipse.fs
./rsc/shaders/mask_box.fs
./rsc/shaders/mask_round.fs
./rsc/shaders/mask_horizontal.fs
./rsc/shaders/mask_vertical.fs
./rsc/shaders/mask_draw.fs
./rsc/shaders/image.vs
./rsc/shaders/imageprocessing.fs
./rsc/fonts/Hack-Regular.ttf
@@ -282,9 +327,12 @@ set(VMIX_RSC_FILES
./rsc/images/transparencygrid.png
./rsc/images/shadow.dds
./rsc/images/glow.dds
./rsc/images/checker.dds
./rsc/images/shadow_perspective.dds
./rsc/images/soft_shadow.dds
./rsc/mesh/disk.ply
./rsc/mesh/circle.ply
./rsc/mesh/corner.ply
./rsc/mesh/shadow.ply
./rsc/mesh/glow.ply
./rsc/mesh/border_round.ply
@@ -297,15 +345,23 @@ set(VMIX_RSC_FILES
./rsc/mesh/border_handles_overlay.ply
./rsc/mesh/border_handles_overlay_filled.ply
./rsc/mesh/border_handles_sharp.ply
./rsc/mesh/border_handles_menu.ply
./rsc/mesh/border_handles_crop.ply
./rsc/mesh/border_handles_shadow.ply
./rsc/mesh/border_large_sharp.ply
./rsc/mesh/border_vertical_overlay.ply
./rsc/mesh/perspective_layer.ply
./rsc/mesh/perspective_axis_left.ply
./rsc/mesh/perspective_axis_right.ply
./rsc/mesh/shadow_perspective.ply
./rsc/mesh/point.ply
./rsc/mesh/square_point.ply
./rsc/mesh/icon_video.ply
./rsc/mesh/icon_image.ply
./rsc/mesh/icon_render.ply
./rsc/mesh/icon_gear.ply
./rsc/mesh/icon_camera.ply
./rsc/mesh/icon_share.ply
./rsc/mesh/icon_clone.ply
./rsc/mesh/icon_vimix.ply
./rsc/mesh/icon_circles.ply
@@ -319,6 +375,8 @@ set(VMIX_RSC_FILES
./rsc/mesh/icon_clock.ply
./rsc/mesh/icon_clock_hand.ply
./rsc/mesh/icon_grid.ply
./rsc/mesh/icon_rightarrow.ply
./rsc/mesh/icon_crop.ply
./rsc/mesh/h_line.ply
./rsc/mesh/h_mark.ply
)
@@ -336,6 +394,7 @@ IF(APPLE)
# create the application
add_executable(${VMIX_BINARY} MACOSX_BUNDLE
${VMIX_SRCS}
./osx/CustomDelegate.m
${IMGUITEXTEDIT_SRC}
${MACOSX_BUNDLE_ICON_FILE}
)
@@ -344,6 +403,11 @@ IF(APPLE)
set(MACOSX_BUNDLE_PLIST_FILE ${CMAKE_SOURCE_DIR}/osx/Info.plist)
set_target_properties(${VMIX_BINARY} PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${MACOSX_BUNDLE_PLIST_FILE})
set(PLATFORM_LIBS
"-framework CoreFoundation"
"-framework Appkit"
)
ELSE(APPLE)
add_executable(${VMIX_BINARY}
@@ -351,6 +415,9 @@ ELSE(APPLE)
${IMGUITEXTEDIT_SRC}
)
set(PLATFORM_LIBS ""
)
ENDIF(APPLE)
@@ -365,7 +432,7 @@ cmrc_add_resource_library(vmix-resources ALIAS vmix::rc NAMESPACE vmix WHENCE rs
message(STATUS "Using 'CMakeRC ' from https://github.com/vector-of-bool/cmrc.git -- ${CMAKE_MODULE_PATH}.")
target_link_libraries(${VMIX_BINARY} LINK_PRIVATE
target_link_libraries(${VMIX_BINARY} LINK_PRIVATE
${GLFW_LIBRARY}
GLAD
glm::glm
@@ -385,7 +452,12 @@ target_link_libraries(${VMIX_BINARY} LINK_PRIVATE
TINYXML2
TINYFD
IMGUI
OSCPACK
vmix::rc
ICU::i18n
ICU::io
ICU::uc
${PLATFORM_LIBS}
)
macro_display_feature_log()
@@ -399,7 +471,7 @@ SET(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_SOURCE_DIR}/README.md")
SET(CPACK_PACKAGE_CONTACT "bruno.herbelin@gmail.com")
SET(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/COPYING.txt")
SET(CPACK_PACKAGE_VERSION_MAJOR "0")
SET(CPACK_PACKAGE_VERSION_MINOR "3")
SET(CPACK_PACKAGE_VERSION_MINOR "4")
SET(CPACK_PACKAGE_VENDOR "Bruno Herbelin")
SET(CPACK_SOURCE_IGNORE_FILES
"/\\\\.git/"
@@ -412,6 +484,8 @@ SET(CPACK_SOURCE_IGNORE_FILES
IF(APPLE)
# include( InstallRequiredSystemLibraries )
# Bundle target
set(CPACK_GENERATOR "DragNDrop")
@@ -433,8 +507,10 @@ IF(APPLE)
# create GST plugins directory in Bundle Resources subfolder
set(plugin_dest_dir vimix.app/Contents/Resources/)
### TODO configure auto to find installation dir of gst
# intall the gst-plugin-scanner program (used by plugins at load time)
install(FILES "/usr/local/Cellar/gstreamer/1.18.0/libexec/gstreamer-1.0/gst-plugin-scanner"
install(FILES "/usr/local/Cellar/gstreamer/1.18.1/libexec/gstreamer-1.0/gst-plugin-scanner"
DESTINATION "${plugin_dest_dir}"
PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
COMPONENT Runtime
@@ -445,23 +521,17 @@ IF(APPLE)
# Install the gst-plugins (all those installed with brew )
install(DIRECTORY "${PKG_GSTREAMER_PLUGIN_DIR}" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
install(DIRECTORY "/usr/local/Cellar/gst-plugins-base/1.18.0/lib/gstreamer-1.0" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
install(DIRECTORY "/usr/local/Cellar/gst-plugins-good/1.18.0/lib/gstreamer-1.0" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
# install(DIRECTORY "/usr/local/Cellar/gst-plugins-bad/1.18.0/lib/gstreamer-1.0" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
install(DIRECTORY "/usr/local/Cellar/gst-plugins-ugly/1.18.0/lib/gstreamer-1.0" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
install(DIRECTORY "/usr/local/Cellar/gst-libav/1.18.0/lib/gstreamer-1.0" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
# install locally recompiled gst-plugins-bad
install(FILES "/Users/herbelin/Development/gst/gst-plugins-bad-1.16.2/build/ext/libde265/libgstde265.dylib" DESTINATION "${plugin_dest_dir}/gstreamer-1.0" COMPONENT Runtime)
install(FILES "/Users/herbelin/Development/gst/gst-plugins-bad-1.16.2/build/ext/x265/libgstx265.dylib" DESTINATION "${plugin_dest_dir}/gstreamer-1.0" COMPONENT Runtime)
install(FILES "/Users/herbelin/Development/gst/gst-plugins-bad-1.16.2/build/sys/applemedia/libgstapplemedia.dylib" DESTINATION "${plugin_dest_dir}/gstreamer-1.0" COMPONENT Runtime)
install(FILES "/Users/herbelin/Development/gst/gst-plugins-bad-1.16.2/build/sys/decklink/libgstdecklink.dylib" DESTINATION "${plugin_dest_dir}/gstreamer-1.0" COMPONENT Runtime)
install(FILES "/Users/herbelin/Development/gst/gst-plugins-bad-1.16.2/build/ext/aom/libgstaom.dylib" DESTINATION "${plugin_dest_dir}/gstreamer-1.0" COMPONENT Runtime)
install(FILES "/Users/herbelin/Development/gst/gst-plugins-bad-1.16.2/build/gst/videoparsers/libgstvideoparsersbad.dylib" DESTINATION "${plugin_dest_dir}/gstreamer-1.0" COMPONENT Runtime)
install(FILES "/Users/herbelin/Development/gst/gst-plugins-bad-1.16.2/build/gst-libs/gst/codecparsers/libgstcodecparsers-1.0.0.dylib" DESTINATION "${plugin_dest_dir}/gstreamer-1.0" COMPONENT Runtime)
install(DIRECTORY "/usr/local/Cellar/gst-plugins-base/1.18.1/lib/gstreamer-1.0" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
install(DIRECTORY "/usr/local/Cellar/gst-plugins-good/1.18.1/lib/gstreamer-1.0" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
install(DIRECTORY "/usr/local/Cellar/gst-plugins-bad/1.18.1_1/lib/gstreamer-1.0" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
install(DIRECTORY "/usr/local/Cellar/gst-plugins-ugly/1.18.1/lib/gstreamer-1.0" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
install(DIRECTORY "/usr/local/Cellar/gst-libav/1.18.1/lib/gstreamer-1.0" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
# install locally recompiled gst-plugins (because not included in brew package)
set(LOCAL_BUILD_BAD "/Users/herbelin/Development/gst/gst-plugins-bad-1.18.0/build")
install(FILES "${LOCAL_BUILD_BAD}/sys/applemedia/libgstapplemedia.dylib" DESTINATION "${plugin_dest_dir}/gstreamer-1.0" COMPONENT Runtime)
install(FILES "${LOCAL_BUILD_BAD}/ext/libde265/libgstde265.dylib" DESTINATION "${plugin_dest_dir}/gstreamer-1.0" COMPONENT Runtime)
install(FILES "${LOCAL_BUILD_BAD}/ext/x265/libgstx265.dylib" DESTINATION "${plugin_dest_dir}/gstreamer-1.0" COMPONENT Runtime)
# package runtime fixup bundle
install(CODE "

292
Connection.cpp Normal file
View File

@@ -0,0 +1,292 @@
#include <thread>
#include <chrono>
#include <vector>
#include <algorithm>
#include "osc/OscOutboundPacketStream.h"
#include "defines.h"
#include "Connection.h"
#include "Settings.h"
#include "Streamer.h"
#include "Log.h"
#ifndef NDEBUG
#define CONNECTION_DEBUG
#endif
Connection::Connection()
{
receiver_ = nullptr;
}
Connection::~Connection()
{
if (receiver_!=nullptr) {
receiver_->Break();
delete receiver_;
}
}
bool Connection::init()
{
// add default info for myself
connections_.push_back(ConnectionInfo());
// try to open a socket at base handshake port
int trial = 0;
while (trial < MAX_HANDSHAKE) {
try {
// increment the port to have unique ports
connections_[0].port_handshake = HANDSHAKE_PORT + trial;
connections_[0].port_stream_request = STREAM_REQUEST_PORT + trial;
connections_[0].port_osc = OSC_DIALOG_PORT + trial;
// try to create listenning socket
// through exception runtime if fails
receiver_ = new UdpListeningReceiveSocket( IpEndpointName( IpEndpointName::ANY_ADDRESS,
connections_[0].port_handshake ), &listener_ );
// validate hostname
connections_[0].name = APP_NAME "@" + NetworkToolkit::hostname() +
"." + std::to_string(connections_[0].port_handshake-HANDSHAKE_PORT);
// all good
trial = MAX_HANDSHAKE;
}
catch (const std::runtime_error&) {
// arg, the receiver could not be initialized
// because the port was not available
receiver_ = nullptr;
}
// try again
trial++;
}
// perfect, we could initialize the receiver
if (receiver_!=nullptr) {
// listen for answers
std::thread(listen).detach();
// regularly check for available streaming hosts
std::thread(ask).detach();
// inform the application settings of our id
Settings::application.instance_id = connections_[0].port_handshake - HANDSHAKE_PORT;
// use or replace instance name from settings
// if (Settings::application.instance_names.count(Settings::application.instance_id))
// connections_[0].name = Settings::application.instance_names[Settings::application.instance_id];
// else
// Settings::application.instance_names[Settings::application.instance_id] = connections_[0].name;
// restore state of Streamer
Streaming::manager().enable( Settings::application.accept_connections );
}
return receiver_ != nullptr;
}
void Connection::terminate()
{
if (receiver_!=nullptr)
receiver_->AsynchronousBreak();
// restore state of Streamer
Streaming::manager().enable( false );
}
int Connection::numHosts () const
{
return connections_.size();
}
ConnectionInfo Connection::info(int index)
{
if (connections_.empty()) {
connections_.push_back(ConnectionInfo());
}
index = CLAMP(index, 0, (int) connections_.size());
return connections_[index];
}
struct hasName: public std::unary_function<ConnectionInfo, bool>
{
inline bool operator()(const ConnectionInfo elem) const {
return (elem.name.compare(_a) == 0);
}
hasName(std::string a) : _a(a) { }
private:
std::string _a;
};
int Connection::index(const std::string &name) const
{
int id = -1;
std::vector<ConnectionInfo>::const_iterator p = std::find_if(connections_.begin(), connections_.end(), hasName(name));
if (p != connections_.end())
id = std::distance(connections_.begin(), p);
return id;
}
int Connection::index(ConnectionInfo i) const
{
int id = -1;
std::vector<ConnectionInfo>::const_iterator p = std::find(connections_.begin(), connections_.end(), i);
if (p != connections_.end())
id = std::distance(connections_.begin(), p);
return id;
}
void Connection::print()
{
for(int i = 0; i<connections_.size(); i++) {
Log::Info(" - %s %s:%d", connections_[i].name.c_str(), connections_[i].address.c_str(), connections_[i].port_handshake);
}
}
void Connection::listen()
{
#ifdef CONNECTION_DEBUG
Log::Info("Accepting handshake on port %d", Connection::manager().connections_[0].port_handshake);
#endif
Connection::manager().receiver_->Run();
}
void Connection::ask()
{
// prepare OSC PING message
char buffer[IP_MTU_SIZE];
osc::OutboundPacketStream p( buffer, IP_MTU_SIZE );
p.Clear();
p << osc::BeginMessage( OSC_PREFIX OSC_PING );
p << Connection::manager().connections_[0].port_handshake;
p << osc::EndMessage;
UdpSocket socket;
socket.SetEnableBroadcast(true);
// loop infinitely
while(true)
{
// broadcast on several ports
for(int i=HANDSHAKE_PORT; i<HANDSHAKE_PORT+MAX_HANDSHAKE; i++)
socket.SendTo( IpEndpointName( i ), p.Data(), p.Size() );
// wait a bit
std::this_thread::sleep_for(std::chrono::milliseconds(500));
// check the list of connections for non responding (disconnected)
std::vector< ConnectionInfo >::iterator it = Connection::manager().connections_.begin();
for(it++; it!=Connection::manager().connections_.end(); ) {
// decrease life score
(*it).alive--;
// erase connection if its life score is negative (not responding too many times)
if ( (*it).alive < 0 ) {
// inform streamer to cancel streaming to this client
Streaming::manager().removeStreams( (*it).name );
// remove from list
it = Connection::manager().connections_.erase(it);
#ifdef CONNECTION_DEBUG
Log::Info("List of connection updated:");
Connection::manager().print();
#endif
}
// loop
else
it++;
}
}
}
void ConnectionRequestListener::ProcessMessage( const osc::ReceivedMessage& m,
const IpEndpointName& remoteEndpoint )
{
char sender[IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH];
remoteEndpoint.AddressAndPortAsString(sender);
// get ip of connection (without port)
std::string remote_ip(sender);
remote_ip = remote_ip.substr(0, remote_ip.find_last_of(":"));
try{
// ping request : reply with pong
if( std::strcmp( m.AddressPattern(), OSC_PREFIX OSC_PING) == 0 ){
// PING message has parameter : port where to reply
osc::ReceivedMessage::const_iterator arg = m.ArgumentsBegin();
int remote_port = (arg++)->AsInt32();
// ignore requests from myself
if ( !NetworkToolkit::is_host_ip(remote_ip)
|| Connection::manager().connections_[0].port_handshake != remote_port) {
// build message
char buffer[IP_MTU_SIZE];
osc::OutboundPacketStream p( buffer, IP_MTU_SIZE );
p.Clear();
p << osc::BeginMessage( OSC_PREFIX OSC_PONG );
p << Connection::manager().connections_[0].name.c_str();
p << Connection::manager().connections_[0].port_handshake;
p << Connection::manager().connections_[0].port_stream_request;
p << Connection::manager().connections_[0].port_osc;
p << osc::EndMessage;
// send OSC message to port indicated by remote
IpEndpointName host( remote_ip.c_str(), remote_port );
UdpTransmitSocket socket( host );
socket.Send( p.Data(), p.Size() );
}
}
// pong response: add info
else if( std::strcmp( m.AddressPattern(), OSC_PREFIX OSC_PONG) == 0 ){
// create info struct
ConnectionInfo info;
info.address = remote_ip;
// add all ports info
osc::ReceivedMessage::const_iterator arg = m.ArgumentsBegin();
info.name = std::string( (arg++)->AsString() );
info.port_handshake = (arg++)->AsInt32();
info.port_stream_request = (arg++)->AsInt32();
info.port_osc = (arg++)->AsInt32();
// do we know this connection ?
int i = Connection::manager().index(info);
if ( i < 0) {
// a new connection! Add to list
Connection::manager().connections_.push_back(info);
// replace instance name in settings
// int id = info.port_handshake - HANDSHAKE_PORT;
// Settings::application.instance_names[id] = info.name;
#ifdef CONNECTION_DEBUG
Log::Info("List of connection updated:");
Connection::manager().print();
#endif
}
else {
// we know this connection: keep its status to ALIVE
Connection::manager().connections_[i].alive = ALIVE;
}
}
}
catch( osc::Exception& e ){
// any parsing errors such as unexpected argument types, or
// missing arguments get thrown as exceptions.
Log::Info("error while parsing message '%s' from %s : %s", m.AddressPattern(), sender, e.what());
}
}

97
Connection.h Normal file
View File

@@ -0,0 +1,97 @@
#ifndef CONNECTION_H
#define CONNECTION_H
#include <vector>
#include "osc/OscReceivedElements.h"
#include "osc/OscPacketListener.h"
#include "ip/UdpSocket.h"
#include "NetworkToolkit.h"
#define ALIVE 3
class ConnectionRequestListener : public osc::OscPacketListener {
protected:
virtual void ProcessMessage( const osc::ReceivedMessage& m,
const IpEndpointName& remoteEndpoint );
};
struct ConnectionInfo {
std::string address;
int port_handshake;
int port_stream_request;
int port_osc;
std::string name;
int alive;
ConnectionInfo () {
address = "127.0.0.1";
port_handshake = HANDSHAKE_PORT;
port_stream_request = STREAM_REQUEST_PORT;
port_osc = OSC_DIALOG_PORT;
name = "";
alive = ALIVE;
}
inline ConnectionInfo& operator = (const ConnectionInfo& o)
{
if (this != &o) {
this->address = o.address;
this->port_handshake = o.port_handshake;
this->port_stream_request = o.port_stream_request;
this->port_osc = o.port_osc;
this->name = o.name;
}
return *this;
}
inline bool operator == (const ConnectionInfo& o) const
{
return this->address.compare(o.address) == 0
&& this->port_handshake == o.port_handshake;
}
};
class Connection
{
friend class ConnectionRequestListener;
// Private Constructor
Connection();
Connection(Connection const& copy); // Not Implemented
Connection& operator=(Connection const& copy); // Not Implemented
public:
static Connection& manager()
{
// The only instance
static Connection _instance;
return _instance;
}
~Connection();
bool init();
void terminate();
int numHosts () const;
int index(ConnectionInfo i) const;
int index(const std::string &name) const;
ConnectionInfo info(int index = 0); // index 0 for self
private:
static void ask();
static void listen();
ConnectionRequestListener listener_;
UdpListeningReceiveSocket *receiver_;
std::vector< ConnectionInfo > connections_;
void print();
};
#endif // CONNECTION_H

View File

@@ -8,6 +8,7 @@
#include "BoundingBoxVisitor.h"
#include "ImageShader.h"
#include "GlmToolkit.h"
#include "Resource.h"
#include "Log.h"
@@ -158,19 +159,29 @@ Handles::Handles(Type type) : Node(), type_(type)
static Mesh *handle_rotation = new Mesh("mesh/border_handles_rotation.ply");
static Mesh *handle_corner = new Mesh("mesh/border_handles_overlay.ply");
static Mesh *handle_scale = new Mesh("mesh/border_handles_scale.ply");
static Mesh *handle_crop = new Mesh("mesh/border_handles_crop.ply");
static Mesh *handle_menu = new Mesh("mesh/border_handles_menu.ply");
static Mesh *handle_shadow = new Mesh("mesh/border_handles_shadow.ply", "images/soft_shadow.dds");
color = glm::vec4( 1.f, 1.f, 0.f, 1.f);
if ( type_ == Handles::ROTATE ) {
handle_ = handle_rotation;
}
else if ( type_ == Handles::SCALE ) {
handle_ = handle_scale;
}
else if ( type_ == Handles::MENU ) {
handle_ = handle_menu;
}
else if ( type_ == Handles::CROP ) {
handle_ = handle_crop;
}
else {
handle_ = handle_corner;
}
color = glm::vec4( 1.f, 1.f, 1.f, 1.f);
corner_ = glm::vec2(0.f, 0.f);
shadow_ = handle_shadow;
}
Handles::~Handles()
@@ -190,6 +201,8 @@ void Handles::draw(glm::mat4 modelview, glm::mat4 projection)
if ( !initialized() ) {
if(handle_ && !handle_->initialized())
handle_->init();
if(shadow_ && !shadow_->initialized())
shadow_->init();
init();
}
@@ -197,6 +210,7 @@ void Handles::draw(glm::mat4 modelview, glm::mat4 projection)
// set color
handle_->shader()->color = color;
handle_active->shader()->color = color;
// extract rotation from modelview
glm::mat4 ctm;
@@ -275,6 +289,7 @@ void Handles::draw(glm::mat4 modelview, glm::mat4 projection)
vec = ( modelview * glm::vec4(1.f, 1.f, 0.f, 1.f) ) + pos;
ctm = GlmToolkit::transform(vec, rot, glm::vec3(1.f));
// 3. draw
shadow_->draw( ctm, projection );
handle_->draw( ctm, projection );
}
else if ( type_ == Handles::SCALE ){
@@ -286,6 +301,31 @@ void Handles::draw(glm::mat4 modelview, glm::mat4 projection)
vec = ( modelview * glm::vec4(1.f, -1.f, 0.f, 1.f) ) + pos;
ctm = GlmToolkit::transform(vec, rot, glm::vec3(mirror.x, mirror.y, 1.f));
// 3. draw
shadow_->draw( ctm, projection );
handle_->draw( ctm, projection );
}
else if ( type_ == Handles::CROP ){
// one icon in bottom right corner
// 1. Fixed displacement by (0.12,0.12) along the rotation..
ctm = GlmToolkit::transform(glm::vec4(0.f), rot, mirror);
glm::vec4 pos = ctm * glm::vec4(mirror.x * 0.12f, mirror.x * 0.12f, 0.f, 1.f);
// 2. ..from the bottom right corner (1,1)
vec = ( modelview * glm::vec4(-1.f, -1.f, 0.f, 1.f) ) + pos;
ctm = GlmToolkit::transform(vec, rot, glm::vec3(mirror.x, mirror.y, 1.f));
// 3. draw
shadow_->draw( ctm, projection );
handle_->draw( ctm, projection );
}
else if ( type_ == Handles::MENU ){
// one icon in top left corner
// 1. Fixed displacement by (-0.12,0.12) along the rotation..
ctm = GlmToolkit::transform(glm::vec4(0.f), rot, mirror);
glm::vec4 pos = ctm * glm::vec4( -0.12f, 0.12f, 0.f, 1.f);
// 2. ..from the top right corner (1,1)
vec = ( modelview * glm::vec4(-1.f, 1.f, 0.f, 1.f) ) + pos;
ctm = GlmToolkit::transform(vec, rot, glm::vec3(1.f));
// 3. draw
shadow_->draw( ctm, projection );
handle_->draw( ctm, projection );
}
}
@@ -301,29 +341,59 @@ void Handles::accept(Visitor& v)
Symbol::Symbol(Type t, glm::vec3 pos) : Node(), type_(t)
{
static Mesh *shadow= new Mesh("mesh/border_handles_shadow.ply", "images/soft_shadow.dds");
static Mesh *shadows[(int)EMPTY+1] = {nullptr};
static Mesh *icons[(int)EMPTY+1] = {nullptr};
if (icons[0] == nullptr) {
icons[CIRCLE_POINT] = new Mesh("mesh/point.ply");
shadows[CIRCLE_POINT] = nullptr;
icons[SQUARE_POINT] = new Mesh("mesh/square_point.ply");
icons[IMAGE] = new Mesh("mesh/icon_image.ply");
icons[VIDEO] = new Mesh("mesh/icon_video.ply");
icons[SESSION] = new Mesh("mesh/icon_vimix.ply");
icons[CLONE] = new Mesh("mesh/icon_clone.ply");
icons[RENDER] = new Mesh("mesh/icon_render.ply");
icons[EMPTY] = new Mesh("mesh/icon_empty.ply");
icons[DOTS] = new Mesh("mesh/icon_dots.ply");
icons[BUSY] = new Mesh("mesh/icon_circles.ply");
icons[LOCK] = new Mesh("mesh/icon_lock.ply");
icons[UNLOCK] = new Mesh("mesh/icon_unlock.ply");
icons[CIRCLE] = new Mesh("mesh/icon_circle.ply");
icons[CLOCK] = new Mesh("mesh/icon_clock.ply");
icons[CLOCK_H] = new Mesh("mesh/icon_clock_hand.ply");
icons[SQUARE] = new Mesh("mesh/icon_square.ply");
icons[CROSS] = new Mesh("mesh/icon_cross.ply");
icons[GRID] = new Mesh("mesh/icon_grid.ply");
shadows[SQUARE_POINT] = nullptr;
icons[IMAGE] = new Mesh("mesh/icon_image.ply");
shadows[IMAGE] = shadow;
icons[VIDEO] = new Mesh("mesh/icon_video.ply");
shadows[VIDEO] = shadow;
icons[SESSION] = new Mesh("mesh/icon_vimix.ply");
shadows[SESSION]= shadow;
icons[CLONE] = new Mesh("mesh/icon_clone.ply");
shadows[CLONE] = shadow;
icons[RENDER] = new Mesh("mesh/icon_render.ply");
shadows[RENDER] = shadow;
icons[PATTERN] = new Mesh("mesh/icon_gear.ply");
shadows[PATTERN]= shadow;
icons[CAMERA] = new Mesh("mesh/icon_camera.ply");
shadows[CAMERA] = shadow;
icons[SHARE] = new Mesh("mesh/icon_share.ply");
shadows[SHARE] = shadow;
icons[DOTS] = new Mesh("mesh/icon_dots.ply");
shadows[DOTS] = nullptr;
icons[BUSY] = new Mesh("mesh/icon_circles.ply");
shadows[BUSY] = nullptr;
icons[LOCK] = new Mesh("mesh/icon_lock.ply");
shadows[LOCK] = shadow;
icons[UNLOCK] = new Mesh("mesh/icon_unlock.ply");
shadows[UNLOCK] = shadow;
icons[ARROWS] = new Mesh("mesh/icon_rightarrow.ply");
shadows[ARROWS] = shadow;
icons[CIRCLE] = new Mesh("mesh/icon_circle.ply");
shadows[CIRCLE] = nullptr;
icons[CLOCK] = new Mesh("mesh/icon_clock.ply");
shadows[CLOCK] = nullptr;
icons[CLOCK_H] = new Mesh("mesh/icon_clock_hand.ply");
shadows[CLOCK_H]= nullptr;
icons[SQUARE] = new Mesh("mesh/icon_square.ply");
shadows[SQUARE] = nullptr;
icons[CROSS] = new Mesh("mesh/icon_cross.ply");
shadows[CROSS] = nullptr;
icons[GRID] = new Mesh("mesh/icon_grid.ply");
shadows[GRID] = nullptr;
icons[EMPTY] = new Mesh("mesh/icon_empty.ply");
shadows[EMPTY] = shadow;
}
symbol_ = icons[type_];
shadow_ = shadows[type_];
translation_ = pos;
color = glm::vec4( 1.f, 1.f, 1.f, 1.f);
}
@@ -338,6 +408,8 @@ void Symbol::draw(glm::mat4 modelview, glm::mat4 projection)
if ( !initialized() ) {
if(symbol_ && !symbol_->initialized())
symbol_->init();
if(shadow_ && !shadow_->initialized())
shadow_->init();
init();
}
@@ -364,6 +436,8 @@ void Symbol::draw(glm::mat4 modelview, glm::mat4 projection)
// generate matrix
ctm = GlmToolkit::transform(tran, rot, sca);
if (shadow_)
shadow_->draw( ctm, projection );
symbol_->draw( ctm, projection);
}
}

View File

@@ -36,7 +36,7 @@ protected:
class Handles : public Node
{
public:
typedef enum { RESIZE = 0, RESIZE_H, RESIZE_V, ROTATE, SCALE } Type;
typedef enum { RESIZE = 0, RESIZE_H, RESIZE_V, ROTATE, SCALE, CROP, MENU } Type;
Handles(Type type);
~Handles();
@@ -51,27 +51,31 @@ public:
glm::vec4 color;
protected:
Primitive *handle_;
Mesh *handle_;
Mesh *shadow_;
glm::vec2 corner_;
Type type_;
};
class Symbol : public Node
{
public:
typedef enum { CIRCLE_POINT = 0, SQUARE_POINT, IMAGE, VIDEO, SESSION, CLONE, RENDER, DOTS, BUSY, LOCK, UNLOCK, CIRCLE, SQUARE, CLOCK, CLOCK_H, GRID, CROSS, EMPTY } Type;
typedef enum { CIRCLE_POINT = 0, SQUARE_POINT, IMAGE, VIDEO, SESSION, CLONE, RENDER, PATTERN, CAMERA, SHARE,
DOTS, BUSY, LOCK, UNLOCK, ARROWS, CROP, CIRCLE, SQUARE, CLOCK, CLOCK_H, GRID, CROSS, EMPTY } Type;
Symbol(Type t = CIRCLE_POINT, glm::vec3 pos = glm::vec3(0.f));
~Symbol();
void draw (glm::mat4 modelview, glm::mat4 projection) override;
void accept (Visitor& v) override;
GlmToolkit::AxisAlignedBoundingBox bbox() const { return symbol_->bbox(); }
Type type() const { return type_; }
glm::vec4 color;
protected:
Mesh *symbol_;
Mesh *shadow_;
Type type_;
};

572
DeviceSource.cpp Normal file
View File

@@ -0,0 +1,572 @@
#include <algorithm>
#include <sstream>
#include <glm/gtc/matrix_transform.hpp>
#include <gst/pbutils/gstdiscoverer.h>
#include <gst/pbutils/pbutils.h>
#include <gst/gst.h>
#include "DeviceSource.h"
#include "defines.h"
#include "ImageShader.h"
#include "ImageProcessingShader.h"
#include "Resource.h"
#include "Decorations.h"
#include "Stream.h"
#include "Visitor.h"
#include "Log.h"
#ifndef NDEBUG
#define DEVICE_DEBUG
//#define GST_DEVICE_DEBUG
#endif
#if defined(APPLE)
std::string gst_plugin_device = "avfvideosrc";
std::string gst_plugin_vidcap = "avfvideosrc capture-screen=true";
#else
std::string gst_plugin_device = "v4l2src";
std::string gst_plugin_vidcap = "ximagesrc";
#endif
////EXAMPLE :
///
//v4l2deviceprovider, udev-probed=(boolean)true,
//device.bus_path=(string)pci-0000:00:14.0-usb-0:2:1.0,
//sysfs.path=(string)/sys/devices/pci0000:00/0000:00:14.0/usb1/1-2/1-2:1.0/video4linux/video0,
//device.bus=(string)usb,
//device.subsystem=(string)video4linux,
//device.vendor.id=(string)1bcf,
//device.vendor.name=(string)"Sunplus\\x20IT\\x20Co\\x20",
//device.product.id=(string)2286,
//device.product.name=(string)"AUSDOM\ FHD\ Camera:\ AUSDOM\ FHD\ C",
//device.serial=(string)Sunplus_IT_Co_AUSDOM_FHD_Camera,
//device.capabilities=(string):capture:,
//device.api=(string)v4l2,
//device.path=(string)/dev/video0,
//v4l2.device.driver=(string)uvcvideo,
//v4l2.device.card=(string)"AUSDOM\ FHD\ Camera:\ AUSDOM\ FHD\ C",
//v4l2.device.bus_info=(string)usb-0000:00:14.0-2,
//v4l2.device.version=(uint)328748,
//v4l2.device.capabilities=(uint)2225078273,
//v4l2.device.device_caps=(uint)69206017;
//Device added: AUSDOM FHD Camera: AUSDOM FHD C - v4l2src device=/dev/video0
//v4l2deviceprovider, udev-probed=(boolean)true,
//device.bus_path=(string)pci-0000:00:14.0-usb-0:4:1.0,
//sysfs.path=(string)/sys/devices/pci0000:00/0000:00:14.0/usb1/1-4/1-4:1.0/video4linux/video2,
//device.bus=(string)usb,
//device.subsystem=(string)video4linux,
//device.vendor.id=(string)046d,
//device.vendor.name=(string)046d,
//device.product.id=(string)080f,
//device.product.name=(string)"UVC\ Camera\ \(046d:080f\)",
//device.serial=(string)046d_080f_3EA77580,
//device.capabilities=(string):capture:,
//device.api=(string)v4l2,
//device.path=(string)/dev/video2,
//v4l2.device.driver=(string)uvcvideo,
//v4l2.device.card=(string)"UVC\ Camera\ \(046d:080f\)",
//v4l2.device.bus_info=(string)usb-0000:00:14.0-4,
//v4l2.device.version=(uint)328748,
//v4l2.device.capabilities=(uint)2225078273,
//v4l2.device.device_caps=(uint)69206017; // decimal of hexadecimal v4l code Device Caps : 0x04200001
//Device added: UVC Camera (046d:080f) - v4l2src device=/dev/video2
std::string pipelineForDevice(GstDevice *device, uint index)
{
std::ostringstream pipe;
const gchar *str = gst_structure_get_string(gst_device_get_properties(device), "device.api");
if (str && gst_plugin_device.find(str) != std::string::npos)
{
pipe << gst_plugin_device;
#if defined(APPLE)
pipe << " device-index=" << index;
#else
str = gst_structure_get_string(gst_device_get_properties(device), "device.path");
if (str)
pipe << " device=" << str;
#endif
}
return pipe.str();
}
gboolean
Device::callback_device_monitor (GstBus *, GstMessage * message, gpointer )
{
GstDevice *device;
gchar *name;
switch (GST_MESSAGE_TYPE (message)) {
case GST_MESSAGE_DEVICE_ADDED: {
gst_message_parse_device_added (message, &device);
name = gst_device_get_display_name (device);
// ignore if already in the list
if ( std::find(manager().src_name_.begin(), manager().src_name_.end(), name) != manager().src_name_.end())
break;
manager().src_name_.push_back(name);
#ifdef GST_DEVICE_DEBUG
gchar *stru = gst_structure_to_string( gst_device_get_properties(device) );
g_print("\nDevice %s plugged : %s\n", name, stru);
g_free (stru);
#endif
g_free (name);
std::string p = pipelineForDevice(device, manager().src_description_.size());
manager().src_description_.push_back(p);
DeviceConfigSet confs = getDeviceConfigs(p);
manager().src_config_.push_back(confs);
manager().list_uptodate_ = false;
gst_object_unref (device);
}
break;
case GST_MESSAGE_DEVICE_REMOVED: {
gst_message_parse_device_removed (message, &device);
name = gst_device_get_display_name (device);
manager().remove(name);
#ifdef GST_DEVICE_DEBUG
g_print("\nDevice %s unplugged\n", name);
#endif
g_free (name);
manager().list_uptodate_ = false;
gst_object_unref (device);
}
break;
default:
break;
}
return G_SOURCE_CONTINUE;
}
void Device::remove(const char *device)
{
std::vector< std::string >::iterator nameit = src_name_.begin();
std::vector< std::string >::iterator descit = src_description_.begin();
std::vector< DeviceConfigSet >::iterator coit = src_config_.begin();
while (nameit != src_name_.end()){
if ( (*nameit).compare(device) == 0 )
{
src_name_.erase(nameit);
src_description_.erase(descit);
src_config_.erase(coit);
break;
}
nameit++;
descit++;
coit++;
}
}
Device::Device()
{
GstBus *bus;
GstCaps *caps;
// create GStreamer device monitor to capture
// when a device is plugged in or out
monitor_ = gst_device_monitor_new ();
bus = gst_device_monitor_get_bus (monitor_);
gst_bus_add_watch (bus, callback_device_monitor, NULL);
gst_object_unref (bus);
caps = gst_caps_new_empty_simple ("video/x-raw");
gst_device_monitor_add_filter (monitor_, "Video/Source", caps);
gst_caps_unref (caps);
gst_device_monitor_set_show_all_devices(monitor_, true);
gst_device_monitor_start (monitor_);
// initial fill of the list
GList *devices = gst_device_monitor_get_devices(monitor_);
GList *tmp;
for (tmp = devices; tmp ; tmp = tmp->next ) {
GstDevice *device = (GstDevice *) tmp->data;
gchar *name = gst_device_get_display_name (device);
src_name_.push_back(name);
g_free (name);
#ifdef GST_DEVICE_DEBUG
gchar *stru = gst_structure_to_string( gst_device_get_properties(device) );
g_print("\nDevice %s already plugged : %s", name, stru);
g_free (stru);
#endif
std::string p = pipelineForDevice(device, src_description_.size());
src_description_.push_back(p);
DeviceConfigSet confs = getDeviceConfigs(p);
src_config_.push_back(confs);
}
g_list_free(devices);
// Add config for plugged screen
src_name_.push_back("Screen capture");
src_description_.push_back(gst_plugin_vidcap);
// Try to auto find resolution
DeviceConfigSet confs = getDeviceConfigs(gst_plugin_vidcap);
if (!confs.empty()) {
// fix the framerate (otherwise at 1 FPS
DeviceConfig best = *confs.rbegin();
DeviceConfigSet confscreen;
best.fps_numerator = 15;
confscreen.insert(best);
src_config_.push_back(confscreen);
}
// TODO Use lib glfw to get monitors
// TODO Detect auto removal of monitors
list_uptodate_ = true;
}
int Device::numDevices() const
{
return src_name_.size();
}
bool Device::exists(const std::string &device) const
{
std::vector< std::string >::const_iterator d = std::find(src_name_.begin(), src_name_.end(), device);
return d != src_name_.end();
}
struct hasDevice: public std::unary_function<DeviceSource*, bool>
{
inline bool operator()(const DeviceSource* elem) const {
return (elem && elem->device() == _d);
}
hasDevice(std::string d) : _d(d) { }
private:
std::string _d;
};
Source *Device::createSource(const std::string &device) const
{
Source *s = nullptr;
// find if a DeviceSource with this device is already registered
std::list< DeviceSource *>::const_iterator d = std::find_if(device_sources_.begin(), device_sources_.end(), hasDevice(device));
// if already registered, clone the device source
if ( d != device_sources_.end()) {
CloneSource *cs = (*d)->clone();
s = cs;
}
// otherwise, we are free to create a new device source
else {
DeviceSource *ds = new DeviceSource();
ds->setDevice(device);
s = ds;
}
return s;
}
bool Device::unplugged(const std::string &device) const
{
if (list_uptodate_)
return false;
return !exists(device);
}
std::string Device::name(int index) const
{
if (index > -1 && index < (int) src_name_.size())
return src_name_[index];
else
return "";
}
std::string Device::description(int index) const
{
if (index > -1 && index < (int) src_description_.size())
return src_description_[index];
else
return "";
}
DeviceConfigSet Device::config(int index) const
{
if (index > -1 && index < (int) src_config_.size())
return src_config_[index];
else
return DeviceConfigSet();
}
int Device::index(const std::string &device) const
{
int i = -1;
std::vector< std::string >::const_iterator p = std::find(src_name_.begin(), src_name_.end(), device);
if (p != src_name_.end())
i = std::distance(src_name_.begin(), p);
return i;
}
DeviceSource::DeviceSource() : StreamSource()
{
// create stream
stream_ = new Stream;
// set symbol
symbol_ = new Symbol(Symbol::CAMERA, glm::vec3(0.8f, 0.8f, 0.01f));
}
DeviceSource::~DeviceSource()
{
// unregister this device source
Device::manager().device_sources_.remove(this);
}
void DeviceSource::setDevice(const std::string &devicename)
{
device_ = devicename;
Log::Notify("Creating Source with device '%s'", device_.c_str());
int index = Device::manager().index(device_);
if (index > -1) {
// register this device source
Device::manager().device_sources_.push_back(this);
// start filling in the gstreamer pipeline
std::ostringstream pipeline;
pipeline << Device::manager().description(index);
// test the device and get config
DeviceConfigSet confs = Device::manager().config(index);
#ifdef DEVICE_DEBUG
Log::Info("Device %s supported configs:", devicename.c_str());
for( DeviceConfigSet::iterator it = confs.begin(); it != confs.end(); it++ ){
float fps = static_cast<float>((*it).fps_numerator) / static_cast<float>((*it).fps_denominator);
Log::Info(" - %s %s %d x %d %.1f fps", (*it).stream.c_str(), (*it).format.c_str(), (*it).width, (*it).height, fps);
}
#endif
DeviceConfig best = *confs.rbegin();
float fps = static_cast<float>(best.fps_numerator) / static_cast<float>(best.fps_denominator);
Log::Info("Device %s selected its optimal config: %s %s %dx%d@%.1ffps", device_.c_str(), best.stream.c_str(), best.format.c_str(), best.width, best.height, fps);
pipeline << " ! " << best.stream;
if (!best.format.empty())
pipeline << ",format=" << best.format;
pipeline << ",framerate=" << best.fps_numerator << "/" << best.fps_denominator;
pipeline << ",width=" << best.width;
pipeline << ",height=" << best.height;
if ( best.stream.find("jpeg") != std::string::npos )
pipeline << " ! jpegdec";
if ( device_.find("Screen") != std::string::npos )
pipeline << " ! videoconvert ! video/x-raw,format=RGB ! queue max-size-buffers=3";
pipeline << " ! videoconvert";
stream_->open( pipeline.str(), best.width, best.height);
stream_->play(true);
}
else
Log::Warning("No such device '%s'", device_.c_str());
}
void DeviceSource::accept(Visitor& v)
{
Source::accept(v);
if (!failed())
v.visit(*this);
}
bool DeviceSource::failed() const
{
return stream_->failed() || Device::manager().unplugged(device_);
}
DeviceConfigSet Device::getDeviceConfigs(const std::string &src_description)
{
DeviceConfigSet configs;
// create dummy pipeline to be tested
std::string description = src_description;
description += " name=devsrc ! fakesink name=sink";
// parse pipeline descriptor
GError *error = NULL;
GstElement *pipeline_ = gst_parse_launch (description.c_str(), &error);
if (error != NULL) {
Log::Warning("DeviceSource Could not construct test pipeline %s:\n%s", description.c_str(), error->message);
g_clear_error (&error);
return configs;
}
// get the pipeline element named "devsrc" from the Device class
GstElement *elem = gst_bin_get_by_name (GST_BIN (pipeline_), "devsrc");
if (elem) {
// initialize the pipeline
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_PAUSED);
if (ret != GST_STATE_CHANGE_FAILURE) {
// get the first pad and its content
GstIterator *iter = gst_element_iterate_src_pads(elem);
GValue vPad = G_VALUE_INIT;
GstPad* ret = NULL;
if (gst_iterator_next(iter, &vPad) == GST_ITERATOR_OK)
{
ret = GST_PAD(g_value_get_object(&vPad));
GstCaps *device_caps = gst_pad_query_caps (ret, NULL);
// loop over all caps offered by the pad
int C = gst_caps_get_size(device_caps);
for (int c = 0; c < C; ++c) {
// get GST cap
GstStructure *decice_cap_struct = gst_caps_get_structure (device_caps, c);
#ifdef GST_DEVICE_DEBUG
gchar *capstext = gst_structure_to_string (decice_cap_struct);
g_print("\nDevice caps: %s", capstext);
g_free(capstext);
#endif
// fill our config
DeviceConfig config;
// not managing opengl texture-target types
// TODO: support input devices texture-target video/x-raw(memory:GLMemory) for improved pipeline
if ( gst_structure_has_field (decice_cap_struct, "texture-target"))
continue;
// NAME : typically video/x-raw or image/jpeg
config.stream = gst_structure_get_name (decice_cap_struct);
// FORMAT : typically BGRA or YUVY
if ( gst_structure_has_field (decice_cap_struct, "format")) {
// get generic value
const GValue *val = gst_structure_get_value(decice_cap_struct, "format");
// if its a list of format string
if ( GST_VALUE_HOLDS_LIST(val)) {
int N = gst_value_list_get_size(val);
for (int n = 0; n < N; n++ ){
std::string f = gst_value_serialize( gst_value_list_get_value(val, n) );
// preference order : 1) RGBx, 2) JPEG, 3) ALL OTHER
// select f if it contains R (e.g. for RGBx) and not already RGB in config
if ( (f.find("R") != std::string::npos) && (config.format.find("R") == std::string::npos ) ) {
config.format = f;
break;
}
// default, take at least one if nothing yet in config
else if ( config.format.empty() )
config.format = f;
}
}
// single format
else {
config.format = gst_value_serialize(val);
}
}
// FRAMERATE : can be a fraction of a list of fractions
if ( gst_structure_has_field (decice_cap_struct, "framerate")) {
// get generic value
const GValue *val = gst_structure_get_value(decice_cap_struct, "framerate");
// if its a single fraction
if ( GST_VALUE_HOLDS_FRACTION(val)) {
config.fps_numerator = gst_value_get_fraction_numerator(val);
config.fps_denominator= gst_value_get_fraction_denominator(val);
}
// if its a range of fraction; take the max
else if ( GST_VALUE_HOLDS_FRACTION_RANGE(val)) {
config.fps_numerator = gst_value_get_fraction_numerator(gst_value_get_fraction_range_max(val));
config.fps_denominator= gst_value_get_fraction_denominator(gst_value_get_fraction_range_max(val));
}
// deal otherwise with a list of fractions; find the max
else if ( GST_VALUE_HOLDS_LIST(val)) {
gdouble fps_max = 1.0;
// loop over all fractions
int N = gst_value_list_get_size(val);
for (int n = 0; n < N; n++ ){
const GValue *frac = gst_value_list_get_value(val, n);
// read one fraction in the list
if ( GST_VALUE_HOLDS_FRACTION(frac)) {
int n = gst_value_get_fraction_numerator(frac);
int d = gst_value_get_fraction_denominator(frac);
// keep only the higher FPS
gdouble f = 1.0;
gst_util_fraction_to_double( n, d, &f );
if ( f > fps_max ) {
config.fps_numerator = n;
config.fps_denominator = d;
fps_max = f;
}
}
}
}
}
// WIDTH and HEIGHT
if ( gst_structure_has_field (decice_cap_struct, "width"))
gst_structure_get_int (decice_cap_struct, "width", &config.width);
if ( gst_structure_has_field (decice_cap_struct, "height"))
gst_structure_get_int (decice_cap_struct, "height", &config.height);
// add this config
configs.insert(config);
}
}
gst_iterator_free(iter);
// terminate pipeline
gst_element_set_state (pipeline_, GST_STATE_NULL);
}
g_object_unref (elem);
}
gst_object_unref (pipeline_);
return configs;
}
glm::ivec2 DeviceSource::icon() const
{
if ( device_.find("Screen") != std::string::npos )
return glm::ivec2(19, 1);
else
return glm::ivec2(2, 14);
}

130
DeviceSource.h Normal file
View File

@@ -0,0 +1,130 @@
#ifndef DEVICESOURCE_H
#define DEVICESOURCE_H
#include <vector>
#include <set>
#include "GstToolkit.h"
#include "StreamSource.h"
class DeviceSource : public StreamSource
{
public:
DeviceSource();
~DeviceSource();
// Source interface
bool failed() const override;
void accept (Visitor& v) override;
// StreamSource interface
Stream *stream() const override { return stream_; }
// specific interface
void setDevice(const std::string &devicename);
inline std::string device() const { return device_; }
glm::ivec2 icon() const override;
private:
std::string device_;
};
struct DeviceConfig {
gint width;
gint height;
gint fps_numerator;
gint fps_denominator;
std::string stream;
std::string format;
DeviceConfig() {
width = 0;
height = 0;
fps_numerator = 1;
fps_denominator = 1;
stream = "";
format = "";
}
inline DeviceConfig& operator = (const DeviceConfig& b)
{
if (this != &b) {
this->width = b.width;
this->height = b.height;
this->fps_numerator = b.fps_numerator;
this->fps_denominator = b.fps_denominator;
this->stream = b.stream;
this->format = b.format;
}
return *this;
}
inline bool operator < (const DeviceConfig b) const
{
int formatscore = this->format.find("R") != std::string::npos ? 2 : 1; // best score for RGBx
int b_formatscore = b.format.find("R") != std::string::npos ? 2 : 1;
float fps = static_cast<float>(this->fps_numerator) / static_cast<float>(this->fps_denominator);
float b_fps = static_cast<float>(b.fps_numerator) / static_cast<float>(b.fps_denominator);
return ( fps * static_cast<float>(this->height * formatscore) < b_fps * static_cast<float>(b.height * b_formatscore));
}
};
struct better_device_comparator
{
inline bool operator () (const DeviceConfig a, const DeviceConfig b) const
{
return (a < b);
}
};
typedef std::set<DeviceConfig, better_device_comparator> DeviceConfigSet;
class Device
{
friend class DeviceSource;
Device();
Device(Device const& copy); // Not Implemented
Device& operator=(Device const& copy); // Not Implemented
public:
static Device& manager()
{
// The only instance
static Device _instance;
return _instance;
}
int numDevices () const;
std::string name (int index) const;
std::string description (int index) const;
DeviceConfigSet config (int index) const;
int index (const std::string &device) const;
bool exists (const std::string &device) const;
bool unplugged (const std::string &device) const;
Source *createSource(const std::string &device) const;
static gboolean callback_device_monitor (GstBus *, GstMessage *, gpointer);
static DeviceConfigSet getDeviceConfigs(const std::string &src_description);
private:
void remove(const char *device);
std::vector< std::string > src_name_;
std::vector< std::string > src_description_;
std::vector< DeviceConfigSet > src_config_;
bool list_uptodate_;
GstDeviceMonitor *monitor_;
std::list< DeviceSource * > device_sources_;
};
#endif // DEVICESOURCE_H

View File

@@ -6,7 +6,7 @@
#include "Scene.h"
DrawVisitor::DrawVisitor(Node *nodetodraw, glm::mat4 projection)
DrawVisitor::DrawVisitor(Node *nodetodraw, glm::mat4 projection, bool force): force_(force)
{
target_ = nodetodraw;
modelview_ = glm::identity<glm::mat4>();
@@ -25,8 +25,13 @@ void DrawVisitor::loop(int num, glm::mat4 transform)
void DrawVisitor::visit(Node &n)
{
// force visible status if required
bool v = n.visible_;
if (force_)
n.visible_ = true;
// draw the target
if ( n.id() == target_->id()) {
if ( target_ && n.id() == target_->id()) {
for (int i = 0; i < num_duplicat_; ++i) {
// draw multiple copies if requested
@@ -37,6 +42,9 @@ void DrawVisitor::visit(Node &n)
done_ = true;
}
// restore visibility
n.visible_ = v;
if (done_) return;
// update transform
@@ -52,7 +60,7 @@ void DrawVisitor::visit(Group &n)
// traverse children
glm::mat4 mv = modelview_;
for (NodeSet::iterator node = n.begin(); !done_ && node != n.end(); node++) {
if ( (*node)->visible_ )
if ( (*node)->visible_ || force_)
(*node)->accept(*this);
modelview_ = mv;
}
@@ -60,17 +68,23 @@ void DrawVisitor::visit(Group &n)
void DrawVisitor::visit(Scene &n)
{
done_ = false;
modelview_ = glm::identity<glm::mat4>();
n.root()->accept(*this);
}
void DrawVisitor::visit(Switch &n)
{
// no need to traverse deeper if this node was drawn already
if (done_) return;
// traverse acive child
glm::mat4 mv = modelview_;
n.activeChild()->accept(*this);
if ( n.activeChild()->visible_ || force_)
n.activeChild()->accept(*this);
modelview_ = mv;
}
void DrawVisitor::visit(Primitive &n)
{
}

View File

@@ -10,11 +10,12 @@ class DrawVisitor : public Visitor
glm::mat4 projection_;
Node *target_;
bool done_;
bool force_;
int num_duplicat_;
glm::mat4 transform_duplicat_;
public:
DrawVisitor(Node *nodetodraw, glm::mat4 projection);
DrawVisitor(Node *nodetodraw, glm::mat4 projection, bool force = false);
void loop(int num, glm::mat4 transform);

View File

@@ -1,15 +1,20 @@
#include <sstream>
#include "FrameBuffer.h"
#include "ImageShader.h"
#include "Resource.h"
#include "Settings.h"
#include "Log.h"
#include <glm/gtc/matrix_transform.hpp>
#include <glad/glad.h>
#include <stb_image.h>
#include <stb_image_write.h>
const char* FrameBuffer::aspect_ratio_name[4] = { "4:3", "3:2", "16:10", "16:9" };
glm::vec2 FrameBuffer::aspect_ratio_size[4] = { glm::vec2(4.f,3.f), glm::vec2(3.f,2.f), glm::vec2(16.f,10.f), glm::vec2(16.f,9.f) };
const char* FrameBuffer::resolution_name[4] = { "720p", "1080p", "1440", "4K" };
const char* FrameBuffer::aspect_ratio_name[5] = { "4:3", "3:2", "16:10", "16:9", "21:9" };
glm::vec2 FrameBuffer::aspect_ratio_size[5] = { glm::vec2(4.f,3.f), glm::vec2(3.f,2.f), glm::vec2(16.f,10.f), glm::vec2(16.f,9.f) , glm::vec2(21.f,9.f) };
const char* FrameBuffer::resolution_name[4] = { "720", "1080", "1440", "2160" };
float FrameBuffer::resolution_height[4] = { 720.f, 1080.f, 1440.f, 2160.f };
@@ -21,12 +26,38 @@ glm::vec3 FrameBuffer::getResolutionFromParameters(int ar, int h)
return res;
}
glm::ivec2 FrameBuffer::getParametersFromResolution(glm::vec3 res)
{
glm::ivec2 p = glm::ivec2(-1);
// get aspect ratio parameter
static int num_ar = ((int)(sizeof(FrameBuffer::aspect_ratio_size) / sizeof(*FrameBuffer::aspect_ratio_size)));
float myratio = res.x / res.y;
for(int ar = 0; ar < num_ar; ar++) {
if ( myratio - (FrameBuffer::aspect_ratio_size[ar].x / FrameBuffer::aspect_ratio_size[ar].y ) < EPSILON){
p.x = ar;
break;
}
}
// get height parameter
static int num_height = ((int)(sizeof(FrameBuffer::resolution_height) / sizeof(*FrameBuffer::resolution_height)));
for(int h = 0; h < num_height; h++) {
if ( res.y - FrameBuffer::resolution_height[h] < 1){
p.y = h;
break;
}
}
return p;
}
FrameBuffer::FrameBuffer(glm::vec3 resolution, bool useAlpha, bool multiSampling):
textureid_(0), intermediate_textureid_(0), framebufferid_(0), intermediate_framebufferid_(0),
use_alpha_(useAlpha), use_multi_sampling_(multiSampling)
{
attrib_.viewport = glm::ivec2(resolution);
attrib_.clear_color = glm::vec4(0.f, 0.f, 0.f, use_alpha_ ? 0.f : 1.f);
setProjectionArea(glm::vec2(1.f, 1.f));
}
FrameBuffer::FrameBuffer(uint width, uint height, bool useAlpha, bool multiSampling):
@@ -35,6 +66,7 @@ FrameBuffer::FrameBuffer(uint width, uint height, bool useAlpha, bool multiSampl
{
attrib_.viewport = glm::ivec2(width, height);
attrib_.clear_color = glm::vec4(0.f, 0.f, 0.f, use_alpha_ ? 0.f : 1.f);
setProjectionArea(glm::vec2(1.f, 1.f));
}
void FrameBuffer::init()
@@ -65,6 +97,8 @@ void FrameBuffer::init()
use_alpha_ ? GL_RGBA8 : GL_RGB8, attrib_.viewport.x, attrib_.viewport.y, GL_TRUE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0);
// attach the multisampled texture to FBO (framebufferid_ currently binded)
@@ -97,6 +131,12 @@ FrameBuffer::~FrameBuffer()
{
if (framebufferid_)
glDeleteFramebuffers(1, &framebufferid_);
if (intermediate_framebufferid_)
glDeleteFramebuffers(1, &intermediate_framebufferid_);
if (textureid_)
glDeleteTextures(1, &textureid_);
if (intermediate_textureid_)
glDeleteTextures(1, &intermediate_textureid_);
}
@@ -114,12 +154,24 @@ float FrameBuffer::aspectRatio() const
}
std::string FrameBuffer::info() const
{
glm::ivec2 p = FrameBuffer::getParametersFromResolution(resolution());
std::ostringstream info;
info << attrib_.viewport.x << "x" << attrib_.viewport.y;
if (p.x > -1)
info << "px, " << FrameBuffer::aspect_ratio_name[p.x];
return info.str();
}
glm::vec3 FrameBuffer::resolution() const
{
return glm::vec3(attrib_.viewport.x, attrib_.viewport.y, 0.f);
}
void FrameBuffer::begin()
void FrameBuffer::begin(bool clear)
{
if (!framebufferid_)
init();
@@ -128,7 +180,8 @@ void FrameBuffer::begin()
Rendering::manager().pushAttrib(attrib_);
glClear(GL_COLOR_BUFFER_BIT);
if (clear)
glClear(GL_COLOR_BUFFER_BIT);
}
void FrameBuffer::end()
@@ -153,7 +206,7 @@ void FrameBuffer::release()
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void FrameBuffer::readPixels()
void FrameBuffer::readPixels(uint8_t *target_data)
{
if (!framebufferid_)
return;
@@ -168,21 +221,24 @@ void FrameBuffer::readPixels()
else
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glReadPixels(0, 0, attrib_.viewport.x, attrib_.viewport.y, (use_alpha_? GL_RGBA : GL_RGB), GL_UNSIGNED_BYTE, 0);
glReadPixels(0, 0, attrib_.viewport.x, attrib_.viewport.y, (use_alpha_? GL_RGBA : GL_RGB), GL_UNSIGNED_BYTE, target_data);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
bool FrameBuffer::blit(FrameBuffer *other)
bool FrameBuffer::blit(FrameBuffer *destination)
{
if (!framebufferid_ || !other || !other->framebufferid_)
if (!framebufferid_ || !destination)
return false;
if (!destination->framebufferid_)
destination->init();
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebufferid_);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, other->framebufferid_);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, destination->framebufferid_);
// blit to the frame buffer object
glBlitFramebuffer(0, 0, attrib_.viewport.x, attrib_.viewport.y,
0, 0, other->width(), other->height(), GL_COLOR_BUFFER_BIT, GL_NEAREST);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
0, 0, destination->width(), destination->height(), GL_COLOR_BUFFER_BIT, GL_NEAREST);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
return true;
}
@@ -223,3 +279,106 @@ void FrameBuffer::checkFramebufferStatus()
}
}
glm::mat4 FrameBuffer::projection() const
{
return projection_;
}
glm::vec2 FrameBuffer::projectionArea() const
{
return projection_area_;
}
void FrameBuffer::setProjectionArea(glm::vec2 c)
{
projection_area_.x = CLAMP(c.x, 0.1f, 1.f);
projection_area_.y = CLAMP(c.y, 0.1f, 1.f);
projection_ = glm::ortho(-projection_area_.x, projection_area_.x, projection_area_.y, -projection_area_.y, -1.f, 1.f);
}
FrameBufferImage::FrameBufferImage(int w, int h) :
rgb(nullptr), width(w), height(h)
{
if (width>0 && height>0)
rgb = new uint8_t[width*height*3];
}
FrameBufferImage::FrameBufferImage(jpegBuffer jpgimg) :
rgb(nullptr), width(0), height(0)
{
int c = 0;
if (jpgimg.buffer != nullptr && jpgimg.len >0)
rgb = stbi_load_from_memory(jpgimg.buffer, jpgimg.len, &width, &height, &c, 3);
}
FrameBufferImage::~FrameBufferImage() {
if (rgb!=nullptr)
delete rgb;
}
FrameBufferImage::jpegBuffer FrameBufferImage::getJpeg()
{
jpegBuffer jpgimg;
// if we hold a valid image
if (rgb!=nullptr && width>0 && height>0) {
// allocate JPEG buffer
// (NB: JPEG will need less than this but we can't know before...)
jpgimg.buffer = (unsigned char *) malloc( width * height * 3 * sizeof(unsigned char));
stbi_write_jpg_to_func( [](void *context, void *data, int size)
{
memcpy(((FrameBufferImage::jpegBuffer*)context)->buffer + ((FrameBufferImage::jpegBuffer*)context)->len, data, size);
((FrameBufferImage::jpegBuffer*)context)->len += size;
}
,&jpgimg, width, height, 3, rgb, FBI_JPEG_QUALITY);
}
return jpgimg;
}
FrameBufferImage *FrameBuffer::image(){
FrameBufferImage *img = nullptr;
// not ready
if (!framebufferid_)
return img;
// allocate image
img = new FrameBufferImage(attrib_.viewport.x, attrib_.viewport.y);
// get pixels into image
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); // set buffer target readpixel
readPixels(img->rgb);
return img;
}
bool FrameBuffer::fill(FrameBufferImage *image)
{
if (!framebufferid_)
init();
// not compatible for RGB
if (use_alpha_ || use_multi_sampling_)
return false;
// invalid image
if ( image == nullptr ||
image->rgb==nullptr ||
image->width !=attrib_.viewport.x ||
image->height!=attrib_.viewport.y )
return false;
// fill texture with image
glBindTexture(GL_TEXTURE_2D, textureid_);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, image->width, image->height,
GL_RGB, GL_UNSIGNED_BYTE, image->rgb);
return true;
}

View File

@@ -1,18 +1,45 @@
#ifndef FRAMEBUFFER_H
#define FRAMEBUFFER_H
#include "Scene.h"
#include "RenderingManager.h"
#define FBI_JPEG_QUALITY 90
/**
* @brief The FrameBufferImage class stores an RGB image in RAM
* Direct access to rgb array, and exchange format to JPEG in RAM
*/
class FrameBufferImage
{
public:
uint8_t *rgb = nullptr;
int width;
int height;
struct jpegBuffer {
unsigned char *buffer = nullptr;
uint len = 0;
};
jpegBuffer getJpeg();
FrameBufferImage(int w, int h);
FrameBufferImage(jpegBuffer jpgimg);
~FrameBufferImage();
};
/**
* @brief The FrameBuffer class holds an OpenGL Frame Buffer Object.
*/
class FrameBuffer {
public:
// size descriptions
static const char* aspect_ratio_name[4];
static glm::vec2 aspect_ratio_size[4];
static const char* aspect_ratio_name[5];
static glm::vec2 aspect_ratio_size[5];
static const char* resolution_name[4];
static float resolution_height[4];
static glm::vec3 getResolutionFromParameters(int ar, int h);
static glm::ivec2 getParametersFromResolution(glm::vec3 res);
// unbind any framebuffer object
static void release();
@@ -21,15 +48,14 @@ public:
~FrameBuffer();
// Bind & push attribs to prepare draw
void begin();
void begin(bool clear = true);
// pop attrib and unbind to end draw
void end();
// blit copy to another, returns true on success
bool blit(FrameBuffer *other);
bool blit(FrameBuffer *destination);
// bind the FrameBuffer in READ and perform glReadPixels
// return the size of the buffer
void readPixels();
// (to be used after preparing a target PBO)
void readPixels(uint8_t* target_data = 0);
// clear color
inline void setClearColor(glm::vec4 color) { attrib_.clear_color = color; }
@@ -40,6 +66,12 @@ public:
inline uint height() const { return attrib_.viewport.y; }
glm::vec3 resolution() const;
float aspectRatio() const;
std::string info() const;
// projection area (crop)
glm::mat4 projection() const;
glm::vec2 projectionArea() const;
void setProjectionArea(glm::vec2 c);
// internal pixel format
inline bool use_alpha() const { return use_alpha_; }
@@ -48,14 +80,21 @@ public:
// index for texturing
uint texture() const;
// get and fill image
FrameBufferImage *image();
bool fill(FrameBufferImage *image);
private:
void init();
void checkFramebufferStatus();
RenderingAttrib attrib_;
glm::mat4 projection_;
glm::vec2 projection_area_;
uint textureid_, intermediate_textureid_;
uint framebufferid_, intermediate_framebufferid_;
bool use_alpha_, use_multi_sampling_;
};

357
FrameGrabber.cpp Normal file
View File

@@ -0,0 +1,357 @@
#include <algorithm>
// Desktop OpenGL function loader
#include <glad/glad.h>
// gstreamer
#include <gst/gstformat.h>
#include <gst/video/video.h>
#include "defines.h"
#include "Log.h"
#include "GstToolkit.h"
#include "FrameBuffer.h"
#include "FrameGrabber.h"
FrameGrabbing::FrameGrabbing(): pbo_index_(0), pbo_next_index_(0), size_(0), width_(0), height_(0), use_alpha_(0), caps_(nullptr)
{
pbo_[0] = 0;
pbo_[1] = 0;
}
FrameGrabbing::~FrameGrabbing()
{
// stop and delete all frame grabbers
clearAll();
// cleanup
if (caps_!=nullptr)
gst_caps_unref (caps_);
if (pbo_[0])
glDeleteBuffers(2, pbo_);
}
void FrameGrabbing::add(FrameGrabber *rec)
{
if (rec != nullptr)
grabbers_.push_back(rec);
}
FrameGrabber *FrameGrabbing::front()
{
if (grabbers_.empty())
return nullptr;
else
return grabbers_.front();
}
struct fgId: public std::unary_function<FrameGrabber*, bool>
{
inline bool operator()(const FrameGrabber* elem) const {
return (elem && elem->id() == _id);
}
fgId(uint64_t id) : _id(id) { }
private:
uint64_t _id;
};
FrameGrabber *FrameGrabbing::get(uint64_t id)
{
if (id > 0 && grabbers_.size() > 0 )
{
std::list<FrameGrabber *>::iterator iter = std::find_if(grabbers_.begin(), grabbers_.end(), fgId(id));
if (iter != grabbers_.end())
return (*iter);
}
return nullptr;
}
void FrameGrabbing::stopAll()
{
std::list<FrameGrabber *>::iterator iter;
for (iter=grabbers_.begin(); iter != grabbers_.end(); iter++ )
(*iter)->stop();
}
void FrameGrabbing::clearAll()
{
std::list<FrameGrabber *>::iterator iter;
for (iter=grabbers_.begin(); iter != grabbers_.end(); )
{
FrameGrabber *rec = *iter;
rec->stop();
iter = grabbers_.erase(iter);
delete rec;
}
}
void FrameGrabbing::grabFrame(FrameBuffer *frame_buffer, float dt)
{
if (frame_buffer == nullptr)
return;
// if different frame buffer from previous frame
if ( frame_buffer->width() != width_ ||
frame_buffer->height() != height_ ||
frame_buffer->use_alpha() != use_alpha_) {
// define stream properties
width_ = frame_buffer->width();
height_ = frame_buffer->height();
use_alpha_ = frame_buffer->use_alpha();
size_ = width_ * height_ * (use_alpha_ ? 4 : 3);
// first time initialization
if ( pbo_[0] == 0 )
glGenBuffers(2, pbo_);
// re-affect pixel buffer object
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo_[1]);
glBufferData(GL_PIXEL_PACK_BUFFER, size_, NULL, GL_STREAM_READ);
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo_[0]);
glBufferData(GL_PIXEL_PACK_BUFFER, size_, NULL, GL_STREAM_READ);
// reset indices
pbo_index_ = 0;
pbo_next_index_ = 0;
// new caps
if (caps_!=nullptr)
gst_caps_unref (caps_);
caps_ = gst_caps_new_simple ("video/x-raw",
"format", G_TYPE_STRING, use_alpha_ ? "RGBA" : "RGB",
"width", G_TYPE_INT, width_,
"height", G_TYPE_INT, height_,
"framerate", GST_TYPE_FRACTION, 30, 1,
NULL);
}
// fill a frame in buffer
if (!grabbers_.empty() && size_ > 0) {
GstBuffer *buffer = nullptr;
// set buffer target for writing in a new frame
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo_[pbo_index_]);
#ifdef USE_GLREADPIXEL
// get frame
frame_buffer->readPixels();
#else
glBindTexture(GL_TEXTURE_2D, frame_buffer->texture());
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
#endif
// update case ; alternating indices
if ( pbo_next_index_ != pbo_index_ ) {
// set buffer target for saving the frame
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo_[pbo_next_index_]);
// new buffer
buffer = gst_buffer_new_and_alloc (size_);
// map gst buffer into a memory WRITE target
GstMapInfo map;
gst_buffer_map (buffer, &map, GST_MAP_WRITE);
// map PBO pixels into a memory READ pointer
unsigned char* ptr = (unsigned char*) glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
// transfer pixels from PBO memory to buffer memory
if (NULL != ptr)
memmove(map.data, ptr, size_);
// un-map
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
gst_buffer_unmap (buffer, &map);
}
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
// alternate indices
pbo_next_index_ = pbo_index_;
pbo_index_ = (pbo_index_ + 1) % 2;
// a frame was successfully grabbed
if (buffer != nullptr) {
// give the frame to all recorders
std::list<FrameGrabber *>::iterator iter = grabbers_.begin();
while (iter != grabbers_.end())
{
FrameGrabber *rec = *iter;
rec->addFrame(buffer, caps_, dt);
if (rec->finished()) {
iter = grabbers_.erase(iter);
delete rec;
}
else
iter++;
}
// unref / free the frame
gst_buffer_unref(buffer);
}
}
}
FrameGrabber::FrameGrabber(): finished_(false), active_(false), accept_buffer_(false),
pipeline_(nullptr), src_(nullptr), caps_(nullptr), timestamp_(0)
{
// unique id
id_ = GlmToolkit::uniqueId();
// configure fix parameter
frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, 30); // 30 FPS
timeframe_ = 2 * frame_duration_;
}
FrameGrabber::~FrameGrabber()
{
if (src_ != nullptr)
gst_object_unref (src_);
if (caps_ != nullptr)
gst_caps_unref (caps_);
if (pipeline_ != nullptr) {
gst_element_set_state (pipeline_, GST_STATE_NULL);
gst_object_unref (pipeline_);
}
}
bool FrameGrabber::finished() const
{
return finished_;
}
bool FrameGrabber::busy() const
{
if (active_)
return accept_buffer_ ? true : false;
else
return false;
}
double FrameGrabber::duration() const
{
return gst_guint64_to_gdouble( GST_TIME_AS_MSECONDS(timestamp_) ) / 1000.0;
}
void FrameGrabber::stop ()
{
// send end of stream
gst_app_src_end_of_stream (src_);
// stop recording
active_ = false;
}
std::string FrameGrabber::info() const
{
if (active_)
return GstToolkit::time_to_string(timestamp_);
else
return "Inactive";
}
// appsrc needs data and we should start sending
void FrameGrabber::callback_need_data (GstAppSrc *, guint , gpointer p)
{
FrameGrabber *grabber = static_cast<FrameGrabber *>(p);
if (grabber)
grabber->accept_buffer_ = true;
}
// appsrc has enough data and we can stop sending
void FrameGrabber::callback_enough_data (GstAppSrc *, gpointer p)
{
FrameGrabber *grabber = static_cast<FrameGrabber *>(p);
if (grabber)
grabber->accept_buffer_ = false;
}
void FrameGrabber::addFrame (GstBuffer *buffer, GstCaps *caps, float dt)
{
// ignore
if (buffer == nullptr)
return;
// first time initialization
if (pipeline_ == nullptr)
init(caps);
// cancel if finished
if (finished_)
return;
// stop if an incompatilble frame buffer given
if ( !gst_caps_is_equal( caps_, caps ))
{
stop();
// Log::Warning("FrameGrabber interrupted: new session (%s)\nincompatible with recording (%s)", gst_caps_to_string(frame.caps), gst_caps_to_string(caps_));
Log::Warning("FrameGrabber interrupted because the resolution changed.");
}
// store a frame if recording is active
if (active_)
{
// calculate dt in ns
timeframe_ += gst_gdouble_to_guint64( dt * 1000000.f );
// if time is passed one frame duration (with 10% margin)
// and if the encoder accepts data
if ( timeframe_ > frame_duration_ - 3000000 && accept_buffer_) {
// set timing of buffer
buffer->pts = timestamp_;
buffer->duration = frame_duration_;
// increment ref counter to make sure the frame remains available
gst_buffer_ref(buffer);
// push
gst_app_src_push_buffer (src_, buffer);
// NB: buffer will be unrefed by the appsrc
accept_buffer_ = false;
// next timestamp
timestamp_ += frame_duration_;
// restart frame counter
timeframe_ = 0;
}
}
// did the recording terminate with sink receiving end-of-stream ?
else {
if (!finished_)
{
// Wait for EOS message
GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline_));
GstMessage *msg = gst_bus_poll(bus, GST_MESSAGE_EOS, GST_TIME_AS_USECONDS(1));
// received EOS
if (msg) {
// stop the pipeline
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_NULL);
if (ret == GST_STATE_CHANGE_FAILURE)
Log::Warning("FrameGrabber Could not stop.");
finished_ = true;
}
}
}
if (finished_)
terminate();
}

128
FrameGrabber.h Normal file
View File

@@ -0,0 +1,128 @@
#ifndef FRAMEGRABBER_H
#define FRAMEGRABBER_H
#include <atomic>
#include <list>
#include <string>
#include <gst/gst.h>
#include <gst/app/gstappsrc.h>
#include "GlmToolkit.h"
// use glReadPixel or glGetTextImage
// read pixels & pbo should be the fastest
// https://stackoverflow.com/questions/38140527/glreadpixels-vs-glgetteximage
#define USE_GLREADPIXEL
class FrameBuffer;
/**
* @brief The FrameGrabber class defines the base class for all recorders
* used to save images or videos from a frame buffer.
*
* Every subclass shall at least implement init() and terminate()
*
* The FrameGrabbing manager calls addFrame() for all its grabbers.
*/
class FrameGrabber
{
friend class FrameGrabbing;
uint64_t id_;
public:
FrameGrabber();
virtual ~FrameGrabber();
inline uint64_t id() const { return id_; }
virtual void stop();
virtual std::string info() const;
virtual double duration() const;
virtual bool finished() const;
virtual bool busy() const;
protected:
// only FrameGrabbing manager can add frame
virtual void addFrame(GstBuffer *buffer, GstCaps *caps, float dt);
// only addFrame method shall call those
virtual void init(GstCaps *caps) = 0;
virtual void terminate() = 0;
// thread-safe testing termination
std::atomic<bool> finished_;
std::atomic<bool> active_;
std::atomic<bool> accept_buffer_;
// gstreamer pipeline
GstElement *pipeline_;
GstAppSrc *src_;
GstCaps *caps_;
GstClockTime timeframe_;
GstClockTime timestamp_;
GstClockTime frame_duration_;
// gstreamer callbacks
static void callback_need_data (GstAppSrc *, guint, gpointer user_data);
static void callback_enough_data (GstAppSrc *, gpointer user_data);
};
/**
* @brief The FrameGrabbing class manages all frame grabbers
*
* Session calls grabFrame after each render
*
*/
class FrameGrabbing
{
friend class Session;
// Private Constructor
FrameGrabbing();
FrameGrabbing(FrameGrabbing const& copy); // Not Implemented
FrameGrabbing& operator=(FrameGrabbing const& copy); // Not Implemented
public:
static FrameGrabbing& manager()
{
// The only instance
static FrameGrabbing _instance;
return _instance;
}
~FrameGrabbing();
inline uint width() const { return width_; }
inline uint height() const { return height_; }
void add(FrameGrabber *rec);
FrameGrabber *front();
FrameGrabber *get(uint64_t id);
void stopAll();
void clearAll();
protected:
// only for friend Session
void grabFrame(FrameBuffer *frame_buffer, float dt);
private:
std::list<FrameGrabber *> grabbers_;
guint pbo_[2];
guint pbo_index_;
guint pbo_next_index_;
guint size_;
guint width_;
guint height_;
bool use_alpha_;
GstCaps *caps_;
};
#endif // FRAMEGRABBER_H

View File

@@ -34,7 +34,7 @@ void GarbageVisitor::visit(Node &n)
// take the node out of the Tree
if (current_)
current_->detatch(&n);
current_->detach(&n);
}

View File

@@ -6,6 +6,17 @@
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/random.hpp>
#include <chrono>
#include <ctime>
uint64_t GlmToolkit::uniqueId()
{
auto duration = std::chrono::high_resolution_clock::now().time_since_epoch();
// 64-bit int 18446744073709551615UL
return std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count() % 1000000000000000000UL;
}
glm::mat4 GlmToolkit::transform(glm::vec3 translation, glm::vec3 rotation, glm::vec3 scale)
{
glm::mat4 View = glm::translate(glm::identity<glm::mat4>(), translation);
@@ -22,7 +33,7 @@ GlmToolkit::AxisAlignedBoundingBox::AxisAlignedBoundingBox() {
mMax = glm::vec3(-1.f);
}
void GlmToolkit::AxisAlignedBoundingBox::extend(const glm::vec3& point) // TODO why ref to point?
void GlmToolkit::AxisAlignedBoundingBox::extend(const glm::vec3& point)
{
if (isNull()) {
mMin = point;
@@ -179,3 +190,17 @@ GlmToolkit::AxisAlignedBoundingBox GlmToolkit::AxisAlignedBoundingBox::transform
return bb;
}
glm::ivec2 GlmToolkit::resolutionFromDescription(int aspectratio, int height)
{
int ar = glm::clamp(aspectratio, 0, 5);
int h = glm::clamp(height, 0, 8);
static glm::vec2 aspect_ratio_size[6] = { glm::vec2(1.f,1.f), glm::vec2(4.f,3.f), glm::vec2(3.f,2.f), glm::vec2(16.f,10.f), glm::vec2(16.f,9.f), glm::vec2(21.f,9.f) };
static float resolution_height[10] = { 16.f, 64.f, 200.f, 320.f, 480.f, 576.f, 720.f, 1080.f, 1440.f, 2160.f };
float width = aspect_ratio_size[ar].x * resolution_height[h] / aspect_ratio_size[ar].y;
glm::ivec2 res = glm::ivec2( width, resolution_height[h]);
return res;
}

View File

@@ -8,8 +8,11 @@
namespace GlmToolkit
{
glm::mat4 transform(glm::vec3 translation, glm::vec3 rotation, glm::vec3 scale);
// get integer with unique id
uint64_t uniqueId();
// get Matrix for these transformation components
glm::mat4 transform(glm::vec3 translation, glm::vec3 rotation, glm::vec3 scale);
class AxisAlignedBoundingBox
{
@@ -44,6 +47,12 @@ public:
AxisAlignedBoundingBox transformed(glm::mat4 m) const;
};
static const char* aspect_ratio_names[6] = { "1:1", "4:3", "3:2", "16:10", "16:9", "21:9" };
static const char* height_names[10] = { "16", "64", "200", "320", "480", "576", "720p", "1080p", "1440", "4K" };
glm::ivec2 resolutionFromDescription(int aspectratio, int height);
}

View File

@@ -2,6 +2,8 @@
#include <iomanip>
using namespace std;
#include <gst/gl/gl.h>
#include "GstToolkit.h"
string GstToolkit::time_to_string(guint64 t, time_string_mode m)
@@ -54,6 +56,16 @@ string GstToolkit::time_to_string(guint64 t, time_string_mode m)
}
std::string GstToolkit::filename_to_uri(std::string path)
{
// set uri to open
gchar *uritmp = gst_filename_to_uri(path.c_str(), NULL);
std::string uri( uritmp );
g_free(uritmp);
return uri;
}
list<string> GstToolkit::all_plugins()
{
list<string> pluginlist;
@@ -122,121 +134,39 @@ string GstToolkit::gst_version()
return oss.str();
}
////EXAMPLE :
///
//v4l2deviceprovider, udev-probed=(boolean)true,
//device.bus_path=(string)pci-0000:00:14.0-usb-0:2:1.0,
//sysfs.path=(string)/sys/devices/pci0000:00/0000:00:14.0/usb1/1-2/1-2:1.0/video4linux/video0,
//device.bus=(string)usb,
//device.subsystem=(string)video4linux,
//device.vendor.id=(string)1bcf,
//device.vendor.name=(string)"Sunplus\\x20IT\\x20Co\\x20",
//device.product.id=(string)2286,
//device.product.name=(string)"AUSDOM\ FHD\ Camera:\ AUSDOM\ FHD\ C",
//device.serial=(string)Sunplus_IT_Co_AUSDOM_FHD_Camera,
//device.capabilities=(string):capture:,
//device.api=(string)v4l2,
//device.path=(string)/dev/video0,
//v4l2.device.driver=(string)uvcvideo,
//v4l2.device.card=(string)"AUSDOM\ FHD\ Camera:\ AUSDOM\ FHD\ C",
//v4l2.device.bus_info=(string)usb-0000:00:14.0-2,
//v4l2.device.version=(uint)328748,
//v4l2.device.capabilities=(uint)2225078273,
//v4l2.device.device_caps=(uint)69206017;
//Device added: AUSDOM FHD Camera: AUSDOM FHD C - v4l2src device=/dev/video0
//v4l2deviceprovider, udev-probed=(boolean)true,
//device.bus_path=(string)pci-0000:00:14.0-usb-0:4:1.0,
//sysfs.path=(string)/sys/devices/pci0000:00/0000:00:14.0/usb1/1-4/1-4:1.0/video4linux/video2,
//device.bus=(string)usb,
//device.subsystem=(string)video4linux,
//device.vendor.id=(string)046d,
//device.vendor.name=(string)046d,
//device.product.id=(string)080f,
//device.product.name=(string)"UVC\ Camera\ \(046d:080f\)",
//device.serial=(string)046d_080f_3EA77580,
//device.capabilities=(string):capture:,
//device.api=(string)v4l2,
//device.path=(string)/dev/video2,
//v4l2.device.driver=(string)uvcvideo,
//v4l2.device.card=(string)"UVC\ Camera\ \(046d:080f\)",
//v4l2.device.bus_info=(string)usb-0000:00:14.0-4,
//v4l2.device.version=(uint)328748,
//v4l2.device.capabilities=(uint)2225078273,
//v4l2.device.device_caps=(uint)69206017; // decimal of hexadecimal v4l code Device Caps : 0x04200001
//Device added: UVC Camera (046d:080f) - v4l2src device=/dev/video2
// see https://developer.ridgerun.com/wiki/index.php?title=GStreamer_modify_the_elements_rank
static gboolean
my_bus_func (GstBus * bus, GstMessage * message, gpointer user_data)
std::list<std::string> GstToolkit::enable_gpu_decoding_plugins()
{
GstDevice *device;
gchar *name;
gchar *stru;
static list<string> pluginslist;
static GstRegistry* plugins_register = nullptr;
switch (GST_MESSAGE_TYPE (message)) {
case GST_MESSAGE_DEVICE_ADDED:
gst_message_parse_device_added (message, &device);
name = gst_device_get_display_name (device);
if ( plugins_register == nullptr ) {
plugins_register = gst_registry_get();
stru = gst_structure_to_string( gst_device_get_properties(device) );
g_print("%s \n", stru);
g_print("Device added: %s - %ssrc device=%s\n", name,
gst_structure_get_string(gst_device_get_properties(device), "device.api"),
gst_structure_get_string(gst_device_get_properties(device), "device.path"));
#if GST_GL_HAVE_PLATFORM_GLX
// https://gstreamer.freedesktop.org/documentation/nvcodec/index.html?gi-language=c#plugin-nvcodec
const char *plugins[6] = { "nvh264dec", "nvh265dec", "nvmpeg2videodec",
"nvmpeg4videodec", "nvvp8dec", "nvvp9dec" };
const int N = 6;
#elif GST_GL_HAVE_PLATFORM_CGL
const char *plugins[1] = { "vtdec_hw" };
const int N = 1;
#else
const char *plugins[0] = { };
const int N = 0;
#endif
g_free (name);
gst_object_unref (device);
break;
case GST_MESSAGE_DEVICE_REMOVED:
gst_message_parse_device_removed (message, &device);
name = gst_device_get_display_name (device);
g_print("Device removed: %s\n", name);
g_free (name);
gst_object_unref (device);
break;
default:
break;
}
for (int i = 0; i < N; i++) {
GstPluginFeature* feature = gst_registry_lookup_feature(plugins_register, plugins[i]);
if(feature != NULL) {
pluginslist.push_front( string( plugins[i] ) );
gst_plugin_feature_set_rank(feature, GST_RANK_PRIMARY + 1);
gst_object_unref(feature);
}
}
}
return G_SOURCE_CONTINUE;
return pluginslist;
}
GstDeviceMonitor *GstToolkit::setup_raw_video_source_device_monitor()
{
GstDeviceMonitor *monitor;
GstBus *bus;
GstCaps *caps;
monitor = gst_device_monitor_new ();
bus = gst_device_monitor_get_bus (monitor);
gst_bus_add_watch (bus, my_bus_func, NULL);
gst_object_unref (bus);
caps = gst_caps_new_empty_simple ("video/x-raw");
gst_device_monitor_add_filter (monitor, "Video/Source", caps);
gst_caps_unref (caps);
gst_device_monitor_set_show_all_devices(monitor, true);
gst_device_monitor_start (monitor);
GList *devices = gst_device_monitor_get_devices(monitor);
GList *tmp;
for (tmp = devices; tmp ; tmp = tmp->next ) {
GstDevice *device = (GstDevice *) tmp->data;
gchar *name = gst_device_get_display_name (device);
g_print("Device already plugged: %s - %ssrc device=%s\n", name,
gst_structure_get_string(gst_device_get_properties(device), "device.api"),
gst_structure_get_string(gst_device_get_properties(device), "device.path"));
g_free (name);
}
g_list_free(devices);
return monitor;
}

View File

@@ -16,16 +16,16 @@ typedef enum {
} time_string_mode;
std::string time_to_string(guint64 t, time_string_mode m = TIME_STRING_ADJUSTED);
std::string filename_to_uri(std::string filename);
std::string gst_version();
std::list<std::string> all_plugins();
std::list<std::string> enable_gpu_decoding_plugins();
std::list<std::string> all_plugin_features(std::string pluginname);
bool enable_feature (std::string name, bool enable);
GstDeviceMonitor *setup_raw_video_source_device_monitor();
}
#endif // __GSTGUI_TOOLKIT_H_

View File

@@ -13,9 +13,11 @@
#endif
#include "imgui_internal.h"
#define MILISECOND 1000000L
#define SECOND 1000000000L
#define MINUTE 60000000000L
#define MILISECOND 1000000UL
#define SECOND 1000000000UL
#define MINUTE 60000000000UL
#include <glad/glad.h>
#include "Resource.h"
#include "FileDialog.h"
@@ -30,14 +32,16 @@ std::map <ImGuiToolkit::font_style, ImFont*>fontmap;
void ImGuiToolkit::ButtonOpenUrl( const char* url, const ImVec2& size_arg )
{
char label[512];
sprintf( label, "%s %s", ICON_FA_EXTERNAL_LINK_ALT, url );
std::string str = SystemToolkit::transliterate( url );
sprintf( label, "%s %s", ICON_FA_EXTERNAL_LINK_ALT, str.c_str() );
if ( ImGui::Button(label, size_arg) )
SystemToolkit::open(url);
}
void ImGuiToolkit::ButtonToggle( const char* label, bool* toggle )
bool ImGuiToolkit::ButtonToggle( const char* label, bool* toggle )
{
ImVec4* colors = ImGui::GetStyle().Colors;
const auto active = *toggle;
@@ -46,8 +50,10 @@ void ImGuiToolkit::ButtonToggle( const char* label, bool* toggle )
ImGui::PushStyleColor( ImGuiCol_ButtonHovered, colors[ImGuiCol_TabHovered] );
ImGui::PushStyleColor( ImGuiCol_ButtonActive, colors[ImGuiCol_Tab] );
}
if( ImGui::Button( label ) ) *toggle = !*toggle;
bool action = ImGui::Button( label );
if( action ) *toggle = !*toggle;
if( active ) ImGui::PopStyleColor( 3 );
return action;
}
@@ -120,7 +126,7 @@ void ImGuiToolkit::Icon(int i, int j)
ImGui::Image((void*)(intptr_t)textureicons, ImVec2(ImGui::GetTextLineHeightWithSpacing(), ImGui::GetTextLineHeightWithSpacing()), uv0, uv1);
}
bool ImGuiToolkit::ButtonIcon(int i, int j)
bool ImGuiToolkit::ButtonIcon(int i, int j, const char *tooltip)
{
// icons.dds is a 20 x 20 grid of icons
if (textureicons == 0)
@@ -133,6 +139,9 @@ bool ImGuiToolkit::ButtonIcon(int i, int j)
bool ret = ImGui::ImageButton((void*)(intptr_t)textureicons, ImVec2(ImGui::GetTextLineHeightWithSpacing(),ImGui::GetTextLineHeightWithSpacing()), uv0, uv1, 3);
ImGui::PopID();
if (tooltip != nullptr)
ImGuiToolkit::ToolTip(tooltip);
return ret;
}
@@ -260,17 +269,31 @@ void ImGuiToolkit::ShowIconsWindow(bool* p_open)
ImGui::End();
}
void ImGuiToolkit::ToolTip(const char* desc)
{
if (ImGui::IsItemHovered())
{
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_DEFAULT);
ImGui::BeginTooltip();
ImGui::Text(desc);
ImGui::EndTooltip();
ImGui::PopFont();
}
}
// Helper to display a little (?) mark which shows a tooltip when hovered.
// In your own code you may want to display an actual icon if you are using a merged icon fonts (see docs/FONTS.txt)
void ImGuiToolkit::HelpMarker(const char* desc)
void ImGuiToolkit::HelpMarker(const char* desc, const char* icon)
{
ImGui::TextDisabled( ICON_FA_QUESTION_CIRCLE );
ImGui::TextDisabled( icon );
if (ImGui::IsItemHovered())
{
ImGui::BeginTooltip();
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_DEFAULT);
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
ImGui::TextUnformatted(desc);
ImGui::PopTextWrapPos();
ImGui::PopFont();
ImGui::EndTooltip();
}
}
@@ -609,9 +632,10 @@ bool ImGuiToolkit::EditPlotLines(const char* label, float *array, int values_cou
return array_changed;
}
bool ImGuiToolkit::EditPlotHistoLines(const char* label, float *histogram_array, float *lines_array, int values_count, float values_min, float values_max, const ImVec2 size)
bool ImGuiToolkit::EditPlotHistoLines(const char* label, float *histogram_array, float *lines_array,
int values_count, float values_min, float values_max, bool *released, const ImVec2 size)
{
static bool active = false;
static bool active = false;
static uint previous_index = UINT32_MAX;
bool array_changed = false;
@@ -634,6 +658,8 @@ bool ImGuiToolkit::EditPlotHistoLines(const char* label, float *histogram_array,
if (!ImGui::ItemAdd(bbox, id))
return false;
*released = false;
// read user input and activate widget
const bool left_mouse_press = ImGui::IsMouseDown(ImGuiMouseButton_Left);
const bool right_mouse_press = ImGui::IsMouseDown(ImGuiMouseButton_Right) | (ImGui::GetIO().KeyAlt & left_mouse_press) ;
@@ -701,6 +727,7 @@ bool ImGuiToolkit::EditPlotHistoLines(const char* label, float *histogram_array,
active = false;
ImGui::ClearActiveID();
previous_index = UINT32_MAX;
*released = true;
}
}
@@ -732,15 +759,21 @@ void ImGuiToolkit::SetFont(ImGuiToolkit::font_style style, const std::string &tt
{
// Font Atlas ImGui Management
ImGuiIO& io = ImGui::GetIO();
GLint max = 0;
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max);
io.Fonts->TexDesiredWidth = max / 2; // optimize use of texture depending on OpenGL drivers
// Setup font config
const ImWchar* glyph_ranges = io.Fonts->GetGlyphRangesDefault();
std::string filename = "fonts/" + ttf_font_name + ".ttf";
std::string fontname = ttf_font_name + ", " + std::to_string(pointsize) + "px";
ImFontConfig font_config;
fontname.copy(font_config.Name, 40);
font_config.FontDataOwnedByAtlas = false; // data will be copied in font atlas
// TODO : calculate oversampling as function of the maximum texture size
font_config.OversampleH = CLAMP( oversample * 2 + 1, 1, 7 );
font_config.OversampleV = CLAMP( oversample, 0, 5 );
if ( max * max < 16777216 ) // hack: try to avoid font textures too larges by disabling oversamplig
oversample = 1;
font_config.OversampleH = CLAMP( oversample, 1, 5 );
font_config.OversampleV = CLAMP( oversample, 1, 5 );
// read font in Resource manager
size_t data_size = 0;
@@ -784,8 +817,11 @@ void ImGuiToolkit::PushFont(ImGuiToolkit::font_style style)
}
void ImGuiToolkit::ShowStats(bool *p_open, int* p_corner)
void ImGuiToolkit::ShowStats(bool *p_open, int* p_corner, bool *p_timer)
{
static guint64 start_time_1_ = gst_util_get_timestamp ();
static guint64 start_time_2_ = gst_util_get_timestamp ();
if (!p_corner || !p_open)
return;
@@ -801,20 +837,44 @@ void ImGuiToolkit::ShowStats(bool *p_open, int* p_corner)
ImGui::SetNextWindowBgAlpha(0.35f); // Transparent background
if (ImGui::Begin("v-mix statistics", NULL, (corner != -1 ? ImGuiWindowFlags_NoMove : 0) | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav))
if (ImGui::Begin("Metrics", NULL, (corner != -1 ? ImGuiWindowFlags_NoMove : 0) | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav))
{
int mode = (*p_timer) ? 1 : 0;
ImGui::SetNextItemWidth(250);
if (ImGui::Combo("##mode", &mode, ICON_FA_TACHOMETER_ALT " Performance\0" ICON_FA_HOURGLASS_HALF " Timers\0") ) {
(*p_timer) = mode > 0;
}
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO);
bool dumm = true;
if (*p_timer) {
guint64 time_ = gst_util_get_timestamp ();
ImGui::Text("Window %.0f x %.0f", io.DisplaySize.x, io.DisplaySize.y);
// ImGui::Text("HiDPI (retina) %s", io.DisplayFramebufferScale.x > 1.f ? "on" : "off");
ImGui::Text("Refresh %.1f FPS", io.Framerate);
ImGui::Text("Memory %s", SystemToolkit::byte_to_string( SystemToolkit::memory_usage()).c_str() );
ImGui::PopFont();
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
ImGui::Text("%s", GstToolkit::time_to_string(time_-start_time_1_, GstToolkit::TIME_STRING_FIXED).c_str());
ImGui::PopFont();
ImGui::SameLine(0, 10);
if (ImGuiToolkit::IconToggle(11, 14, 12, 14, &dumm))
start_time_1_ = time_; // reset timer 1
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
ImGui::Text("%s", GstToolkit::time_to_string(time_-start_time_2_, GstToolkit::TIME_STRING_FIXED).c_str());
ImGui::PopFont();
ImGui::SameLine(0, 10); dumm = true;
if (ImGuiToolkit::IconToggle(11, 14, 12, 14, &dumm))
start_time_1_ = time_; // reset timer 2
}
else {
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO);
ImGui::Text("Window %.0f x %.0f", io.DisplaySize.x, io.DisplaySize.y);
// ImGui::Text("HiDPI (retina) %s", io.DisplayFramebufferScale.x > 1.f ? "on" : "off");
ImGui::Text("Refresh %.1f FPS", io.Framerate);
ImGui::Text("Memory %s", SystemToolkit::byte_to_string( SystemToolkit::memory_usage()).c_str() );
ImGui::PopFont();
}
if (ImGui::BeginPopupContextWindow())
{
if (ImGui::MenuItem("Custom", NULL, corner == -1)) *p_corner = -1;
if (ImGui::MenuItem("Free position", NULL, corner == -1)) *p_corner = -1;
if (ImGui::MenuItem("Top", NULL, corner == 1)) *p_corner = 1;
if (ImGui::MenuItem("Bottom", NULL, corner == 3)) *p_corner = 3;
if (p_open && ImGui::MenuItem("Close")) *p_open = false;

View File

@@ -15,21 +15,22 @@ namespace ImGuiToolkit
void ShowIconsWindow(bool* p_open);
// utility buttons
bool ButtonIcon (int i, int j);
bool ButtonIcon (int i, int j, const char* tooltip = nullptr);
bool ButtonIconToggle (int i, int j, int i_toggle, int j_toggle, bool* toggle);
bool ButtonIconMultistate (std::vector<std::pair<int, int> > icons, int* state);
void ButtonToggle (const char* label, bool* toggle);
bool ButtonToggle(const char* label, bool* toggle);
void ButtonSwitch (const char* label, bool* toggle , const char *help = nullptr);
bool IconToggle (int i, int j, int i_toggle, int j_toggle, bool* toggle, const char *tooltips[] = nullptr);
void ButtonOpenUrl (const char* url, const ImVec2& size_arg = ImVec2(0,0));
void HelpMarker (const char* desc);
void ToolTip (const char* desc);
void HelpMarker (const char* desc, const char* icon = ICON_FA_QUESTION_CIRCLE);
// utility sliders
bool TimelineSlider (const char* label, guint64 *time, guint64 start, guint64 end, guint64 step, const float width);
bool InvisibleSliderInt(const char* label, uint *index, uint min, uint max, const ImVec2 size);
bool EditPlotLines(const char* label, float *array, int values_count, float values_min, float values_max, const ImVec2 size);
bool EditPlotHistoLines(const char* label, float *histogram_array, float *lines_array, int values_count, float values_min, float values_max, const ImVec2 size);
bool EditPlotHistoLines(const char* label, float *histogram_array, float *lines_array, int values_count, float values_min, float values_max, bool *released, const ImVec2 size);
// fonts from ressources 'fonts/'
typedef enum {
@@ -56,7 +57,7 @@ namespace ImGuiToolkit
void SetAccentColor (accent_color color);
struct ImVec4 GetHighlightColor ();
void ShowStats (bool* p_open, int* p_corner);
void ShowStats (bool* p_open, int* p_corner, bool* p_timer);
}

View File

@@ -2,6 +2,8 @@
#include <vector>
#include <algorithm>
#include <sstream>
#include <iomanip>
#include <glm/glm.hpp>
#include <glm/gtc/type_ptr.hpp>
@@ -16,8 +18,12 @@
#include "MediaPlayer.h"
#include "MediaSource.h"
#include "SessionSource.h"
#include "PatternSource.h"
#include "DeviceSource.h"
#include "NetworkSource.h"
#include "Settings.h"
#include "Mixer.h"
#include "ActionManager.h"
#include "imgui.h"
#include "ImGuiToolkit.h"
@@ -37,10 +43,8 @@ void ImGuiVisitor::visit(Node &n)
void ImGuiVisitor::visit(Group &n)
{
// std::string id = std::to_string(n.id());
// if (ImGui::TreeNode(id.c_str(), "Group %d", n.id()))
// {
// MODEL VIEW
// MODEL VIEW
ImGui::PushID(std::to_string(n.id()).c_str());
if (ImGuiToolkit::ButtonIcon(1, 16)) {
n.translation_.x = 0.f;
@@ -48,6 +52,7 @@ void ImGuiVisitor::visit(Group &n)
n.rotation_.z = 0.f;
n.scale_.x = 1.f;
n.scale_.y = 1.f;
Action::manager().store("Geometry Reset", n.id());
}
ImGui::SameLine(0, 10);
ImGui::Text("Geometry");
@@ -55,6 +60,7 @@ void ImGuiVisitor::visit(Group &n)
if (ImGuiToolkit::ButtonIcon(6, 15)) {
n.translation_.x = 0.f;
n.translation_.y = 0.f;
Action::manager().store("Position 0.0, 0.0", n.id());
}
ImGui::SameLine(0, 10);
float translation[2] = { n.translation_.x, n.translation_.y};
@@ -64,16 +70,15 @@ void ImGuiVisitor::visit(Group &n)
n.translation_.x = translation[0];
n.translation_.y = translation[1];
}
if (ImGuiToolkit::ButtonIcon(18, 9))
n.rotation_.z = 0.f;
ImGui::SameLine(0, 10);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
ImGui::SliderAngle("Angle", &(n.rotation_.z), -180.f, 180.f) ;
if (ImGui::IsItemDeactivatedAfterEdit()){
std::ostringstream oss;
oss << "Position " << std::setprecision(3) << n.translation_.x << ", " << n.translation_.y;
Action::manager().store(oss.str(), n.id());
}
if (ImGuiToolkit::ButtonIcon(3, 15)) {
n.scale_.x = 1.f;
n.scale_.y = 1.f;
Action::manager().store("Scale 1.0 x 1.0", n.id());
}
ImGui::SameLine(0, 10);
float scale[2] = { n.scale_.x, n.scale_.y} ;
@@ -83,14 +88,27 @@ void ImGuiVisitor::visit(Group &n)
n.scale_.x = CLAMP_SCALE(scale[0]);
n.scale_.y = CLAMP_SCALE(scale[1]);
}
if (ImGui::IsItemDeactivatedAfterEdit()){
std::ostringstream oss;
oss << "Scale " << std::setprecision(3) << n.scale_.x << " x " << n.scale_.y;
Action::manager().store(oss.str(), n.id());
}
// // loop over members of a group
// for (NodeSet::iterator node = n.begin(); node != n.end(); node++) {
// (*node)->accept(*this);
// }
if (ImGuiToolkit::ButtonIcon(18, 9)){
n.rotation_.z = 0.f;
Action::manager().store("Angle 0.0", n.id());
}
ImGui::SameLine(0, 10);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
ImGui::SliderAngle("Angle", &(n.rotation_.z), -180.f, 180.f) ;
if (ImGui::IsItemDeactivatedAfterEdit()) {
std::ostringstream oss;
oss << "Angle " << std::setprecision(3) << n.rotation_.z * 180.f / M_PI;
Action::manager().store(oss.str(), n.id());
}
// ImGui::TreePop();
// }
ImGui::PopID();
// spacing
ImGui::Spacing();
@@ -113,7 +131,7 @@ void ImGuiVisitor::visit(Scene &n)
void ImGuiVisitor::visit(Primitive &n)
{
ImGui::PushID(n.id());
ImGui::PushID(std::to_string(n.id()).c_str());
ImGui::Text("Primitive %d", n.id());
n.shader()->accept(*this);
@@ -141,8 +159,9 @@ void ImGuiVisitor::visit(MediaPlayer &n)
void ImGuiVisitor::visit(Shader &n)
{
ImGui::PushID(n.id());
ImGui::PushID(std::to_string(n.id()).c_str());
// Base color
// if (ImGuiToolkit::ButtonIcon(10, 2)) {
// n.blending = Shader::BLEND_OPACITY;
// n.color = glm::vec4(1.f, 1.f, 1.f, 1.f);
@@ -152,59 +171,95 @@ void ImGuiVisitor::visit(Shader &n)
// ImGui::SameLine(0, 5);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
int mode = n.blending;
if (ImGui::Combo("Blending", &mode, "Normal\0Screen\0Inverse\0Addition\0Subtract\0") )
if (ImGui::Combo("Blending", &mode, "Normal\0Screen\0Inverse\0Addition\0Subtract\0") ) {
n.blending = Shader::BlendMode(mode);
ImGui::PopID();
}
void ImGuiVisitor::visit(ImageShader &n)
{
ImGui::PushID(n.id());
// get index of the mask used in this ImageShader
int item_current = n.mask;
// if (ImGuiToolkit::ButtonIcon(10, 3)) n.mask = 0;
// ImGui::SameLine(0, 10);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
// combo list of masks
if ( ImGui::Combo("Mask", &item_current, ImageShader::mask_names, IM_ARRAYSIZE(ImageShader::mask_names) ) )
{
if (item_current < (int) ImageShader::mask_presets.size())
n.mask = item_current;
else {
// TODO ask for custom mask
std::ostringstream oss;
oss << "Blending ";
switch(n.blending) {
case Shader::BLEND_OPACITY:
oss<<"Normal";
break;
case Shader::BLEND_ADD:
oss<<"Screen";
break;
case Shader::BLEND_SUBSTRACT:
oss<<"Inverse";
break;
case Shader::BLEND_LAYER_ADD:
oss<<"Addition";
break;
case Shader::BLEND_LAYER_SUBSTRACT:
oss<<"Subtract";
break;
case Shader::BLEND_CUSTOM:
oss<<"Custom";
break;
}
Action::manager().store(oss.str(), n.id());
}
ImGui::PopID();
}
//void ImGuiVisitor::visit(ImageShader &n)
//{
// ImGui::PushID(std::to_string(n.id()).c_str());
// // get index of the mask used in this ImageShader
// int item_current = n.mask;
//// if (ImGuiToolkit::ButtonIcon(10, 3)) n.mask = 0;
//// ImGui::SameLine(0, 10);
// ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
// // combo list of masks
// if ( ImGui::Combo("Mask", &item_current, ImageShader::mask_names, IM_ARRAYSIZE(ImageShader::mask_names) ) )
// {
// if (item_current < (int) ImageShader::mask_presets.size())
// n.mask = item_current;
// else {
// // TODO ask for custom mask
// }
// Action::manager().store("Mask "+ std::string(ImageShader::mask_names[n.mask]), n.id());
// }
// ImGui::PopID();
//}
void ImGuiVisitor::visit(ImageProcessingShader &n)
{
ImGui::PushID(n.id());
ImGui::PushID(std::to_string(n.id()).c_str());
if (ImGuiToolkit::ButtonIcon(6, 2)) {
ImageProcessingShader defaultvalues;
n = defaultvalues;
Action::manager().store("Reset Filters", n.id());
}
ImGui::SameLine(0, 10);
ImGui::Text("Filters");
if (ImGuiToolkit::ButtonIcon(6, 4)) n.gamma = glm::vec4(1.f, 1.f, 1.f, 1.f);
if (ImGuiToolkit::ButtonIcon(6, 4)) {
n.gamma = glm::vec4(1.f, 1.f, 1.f, 1.f);
Action::manager().store("Gamma & Color", n.id());
}
ImGui::SameLine(0, 10);
ImGui::ColorEdit3("GammaColor", glm::value_ptr(n.gamma), ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel) ;
ImGui::ColorEdit3("Gamma Color", glm::value_ptr(n.gamma), ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel) ;
if (ImGui::IsItemDeactivatedAfterEdit())
Action::manager().store("Gamma Color changed", n.id());
ImGui::SameLine(0, 5);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
ImGui::SliderFloat("Gamma", &n.gamma.w, 0.5f, 10.f, "%.2f", 2.f);
if (ImGui::IsItemDeactivatedAfterEdit()){
std::ostringstream oss;
oss << "Gamma " << std::setprecision(2) << n.gamma.w;
Action::manager().store(oss.str(), n.id());
}
// ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
// ImGui::SliderFloat4("Levels", glm::value_ptr(n.levels), 0.0, 1.0);
if (ImGuiToolkit::ButtonIcon(4, 1)) {
if (ImGuiToolkit::ButtonIcon(5, 16)) {
n.brightness = 0.f;
n.contrast = 0.f;
Action::manager().store("B & C 0.0 0.0", n.id());
}
ImGui::SameLine(0, 10);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
@@ -214,51 +269,115 @@ void ImGuiVisitor::visit(ImageProcessingShader &n)
n.brightness = bc[0];
n.contrast = bc[1];
}
if (ImGui::IsItemDeactivatedAfterEdit()){
std::ostringstream oss;
oss << "B & C " << std::setprecision(2) << n.brightness << " " << n.contrast;
Action::manager().store(oss.str(), n.id());
}
if (ImGuiToolkit::ButtonIcon(2, 1)) n.saturation = 0.f;
if (ImGuiToolkit::ButtonIcon(9, 16)) {
n.saturation = 0.f;
Action::manager().store("Saturation 0.0", n.id());
}
ImGui::SameLine(0, 10);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
ImGui::SliderFloat("Saturation", &n.saturation, -1.0, 1.0);
if (ImGui::IsItemDeactivatedAfterEdit()){
std::ostringstream oss;
oss << "Saturation " << std::setprecision(2) << n.saturation;
Action::manager().store(oss.str(), n.id());
}
if (ImGuiToolkit::ButtonIcon(12, 4)) n.hueshift = 0.f;
if (ImGuiToolkit::ButtonIcon(12, 4)) {
n.hueshift = 0.f;
Action::manager().store("Hue shift 0.0", n.id());
}
ImGui::SameLine(0, 10);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
ImGui::SliderFloat("Hue shift", &n.hueshift, 0.0, 1.0);
if (ImGui::IsItemDeactivatedAfterEdit()){
std::ostringstream oss;
oss << "Hue shift " << std::setprecision(2) << n.hueshift;
Action::manager().store(oss.str(), n.id());
}
if (ImGuiToolkit::ButtonIcon(3, 1)) n.lumakey = 0.f;
ImGui::SameLine(0, 10);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
ImGui::SliderFloat("Lumakey", &n.lumakey, 0.0, 1.0);
if (ImGuiToolkit::ButtonIcon(8, 1)) n.threshold = 0.f;
ImGui::SameLine(0, 10);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
ImGui::SliderFloat("Threshold", &n.threshold, 0.0, 1.0, n.threshold < 0.001 ? "None" : "%.2f");
if (ImGuiToolkit::ButtonIcon(18, 1)) n.nbColors = 0;
if (ImGuiToolkit::ButtonIcon(18, 1)) {
n.nbColors = 0;
Action::manager().store("Posterize None", n.id());
}
ImGui::SameLine(0, 10);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
ImGui::SliderInt("Posterize", &n.nbColors, 0, 16, n.nbColors == 0 ? "None" : "%d colors");
if (ImGui::IsItemDeactivatedAfterEdit()){
std::ostringstream oss;
oss << "Posterize ";
if (n.nbColors == 0) oss << "None"; else oss << n.nbColors;
Action::manager().store(oss.str(), n.id());
}
if (ImGuiToolkit::ButtonIcon(1, 7)) n.filterid = 0;
if (ImGuiToolkit::ButtonIcon(8, 1)) {
n.threshold = 0.f;
Action::manager().store("Threshold None", n.id());
}
ImGui::SameLine(0, 10);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
ImGui::Combo("Filter", &n.filterid, ImageProcessingShader::filter_names, IM_ARRAYSIZE(ImageProcessingShader::filter_names) );
ImGui::SliderFloat("Threshold", &n.threshold, 0.0, 1.0, n.threshold < 0.001 ? "None" : "%.2f");
if (ImGui::IsItemDeactivatedAfterEdit()){
std::ostringstream oss;
oss << "Threshold ";
if (n.threshold < 0.001) oss << "None"; else oss << std::setprecision(2) << n.threshold;
Action::manager().store(oss.str(), n.id());
}
if (ImGuiToolkit::ButtonIcon(7, 1)) n.invert = 0;
if (ImGuiToolkit::ButtonIcon(3, 1)) {
n.lumakey = 0.f;
Action::manager().store("Lumakey 0.0", n.id());
}
ImGui::SameLine(0, 10);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
ImGui::Combo("Invert", &n.invert, "None\0Invert Color\0Invert Luminance\0");
ImGui::SliderFloat("Lumakey", &n.lumakey, 0.0, 1.0);
if (ImGui::IsItemDeactivatedAfterEdit()){
std::ostringstream oss;
oss << "Lumakey " << std::setprecision(2) << n.lumakey;
Action::manager().store(oss.str(), n.id());
}
if (ImGuiToolkit::ButtonIcon(13, 4)) {
n.chromakey = glm::vec4(0.f, 0.8f, 0.f, 1.f);
n.chromadelta = 0.f;
Action::manager().store("Chromakey & Color Reset", n.id());
}
ImGui::SameLine(0, 10);
ImGui::ColorEdit3("Chroma color", glm::value_ptr(n.chromakey), ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel ) ;
ImGui::ColorEdit3("Chroma color", glm::value_ptr(n.chromakey), ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel ) ;
if (ImGui::IsItemDeactivatedAfterEdit())
Action::manager().store("Chroma color changed", n.id());
ImGui::SameLine(0, 5);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
ImGui::SliderFloat("Chromakey", &n.chromadelta, 0.0, 1.0, n.chromadelta < 0.001 ? "None" : "Tolerance %.2f");
if (ImGui::IsItemDeactivatedAfterEdit()){
std::ostringstream oss;
oss << "Chromakey ";
if (n.chromadelta < 0.001) oss << "None"; else oss << std::setprecision(2) << n.chromadelta;
Action::manager().store(oss.str(), n.id());
}
if (ImGuiToolkit::ButtonIcon(6, 16)) {
n.invert = 0;
Action::manager().store("Invert None", n.id());
}
ImGui::SameLine(0, 10);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
if (ImGui::Combo("Invert", &n.invert, "None\0Invert Color\0Invert Luminance\0"))
Action::manager().store("Invert " + std::string(n.invert<1 ? "None": (n.invert>1 ? "Luminance" : "Color")), n.id());
if (ImGuiToolkit::ButtonIcon(1, 7)) {
n.filterid = 0;
Action::manager().store("Filter None", n.id());
}
ImGui::SameLine(0, 10);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
if (ImGui::Combo("Filter", &n.filterid, ImageProcessingShader::filter_names, IM_ARRAYSIZE(ImageProcessingShader::filter_names) ) )
Action::manager().store("Filter " + std::string(ImageProcessingShader::filter_names[n.filterid]), n.id());
ImGui::PopID();
@@ -268,20 +387,48 @@ void ImGuiVisitor::visit(ImageProcessingShader &n)
void ImGuiVisitor::visit (Source& s)
{
ImGui::PushID(std::to_string(s.id()).c_str());
// blending
s.blendingShader()->accept(*this);
// preview
float preview_width = ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN;
ImVec2 imagesize ( preview_width, preview_width / s.frame()->aspectRatio());
ImGui::Image((void*)(uintptr_t) s.frame()->texture(), imagesize);
float width = preview_width;
float height = s.frame()->projectionArea().y * width / ( s.frame()->projectionArea().x * s.frame()->aspectRatio());
if (height > 230) {
height = 230;
width = height * s.frame()->aspectRatio() * ( s.frame()->projectionArea().x / s.frame()->projectionArea().y);
}
ImGui::Image((void*)(uintptr_t) s.frame()->texture(), ImVec2(width, height));
ImVec2 pos = ImGui::GetCursorPos(); // remember where we were...
// inform on visibility status
ImGui::SetCursorPos( ImVec2(preview_width + 20, pos.y -height ) );
if (s.active()) {
if (s.blendingShader()->color.a > 0.f)
ImGuiToolkit::HelpMarker("Visible", ICON_FA_EYE);
else
ImGuiToolkit::HelpMarker("Not visible", ICON_FA_EYE_SLASH);
}
else
ImGuiToolkit::HelpMarker("Inactive", ICON_FA_SNOWFLAKE);
// Inform on status of lock
ImGui::SetCursorPos( ImVec2(preview_width + 20, pos.y -height + ImGui::GetFrameHeight()) );
if (s.locked())
ImGuiToolkit::HelpMarker("Locked", ICON_FA_LOCK);
else
ImGuiToolkit::HelpMarker("Unlocked", ICON_FA_LOCK_OPEN);
// toggle enable/disable image processing
bool on = s.imageProcessingEnabled();
ImGui::SetCursorPos( ImVec2(preview_width + 15, pos.y -ImGui::GetFrameHeight() ) );
ImGuiToolkit::ButtonToggle(ICON_FA_MAGIC, &on);
if ( ImGuiToolkit::ButtonToggle(ICON_FA_MAGIC, &on) ){
std::ostringstream oss;
oss << s.name() << ": " << ( on ? "Enable Filter" : "Disable Filter");
Action::manager().store(oss.str(), s.id());
}
s.setImageProcessingEnabled(on);
ImGui::SetCursorPos(pos); // ...come back
@@ -291,8 +438,10 @@ void ImGuiVisitor::visit (Source& s)
s.processingShader()->accept(*this);
// geometry direct control
s.groupNode(View::GEOMETRY)->accept(*this);
// s.groupNode(View::GEOMETRY)->accept(*this);
// s.groupNode((View::Mode) Settings::application.current_view)->accept(*this);
ImGui::PopID();
}
void ImGuiVisitor::visit (MediaSource& s)
@@ -306,18 +455,18 @@ void ImGuiVisitor::visit (MediaSource& s)
ImGuiToolkit::Icon(18,13);
ImGui::SameLine(0, 10);
ImGui::Text("Video File");
if ( ImGui::Button(IMGUI_TITLE_MEDIAPLAYER, ImVec2(IMGUI_RIGHT_ALIGN, 0)) ) {
UserInterface::manager().showMediaPlayer( s.mediaplayer());
}
}
if ( ImGui::Button(IMGUI_TITLE_MEDIAPLAYER, ImVec2(IMGUI_RIGHT_ALIGN, 0)) )
UserInterface::manager().showMediaPlayer( s.mediaplayer());
ImGuiToolkit::ButtonOpenUrl( SystemToolkit::path_filename(s.path()).c_str(), ImVec2(IMGUI_RIGHT_ALIGN, 0) );
}
void ImGuiVisitor::visit (SessionSource& s)
{
ImGuiToolkit::Icon(4,9);
ImGuiToolkit::Icon(s.icon().x, s.icon().y);
ImGui::SameLine(0, 10);
ImGui::Text("Session File");
ImGui::Text("%s", SystemToolkit::base_filename(s.path()).c_str());
if (ImGuiToolkit::ButtonIcon(3, 2)) s.session()->setFading(0.f);
float f = s.session()->fading();
@@ -325,6 +474,11 @@ void ImGuiVisitor::visit (SessionSource& s)
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
if (ImGui::SliderFloat("Fading", &f, 0.0, 1.0, f < 0.001 ? "None" : "%.2f") )
s.session()->setFading(f);
if (ImGui::IsItemDeactivatedAfterEdit()){
std::ostringstream oss;
oss << s.name() << ": Fading " << std::setprecision(2) << f;
Action::manager().store(oss.str(), s.id());
}
if ( ImGui::Button( ICON_FA_FILE_UPLOAD " Make Current", ImVec2(IMGUI_RIGHT_ALIGN, 0)) )
Mixer::manager().set( s.detach() );
@@ -336,7 +490,7 @@ void ImGuiVisitor::visit (SessionSource& s)
void ImGuiVisitor::visit (RenderSource& s)
{
ImGuiToolkit::Icon(19,1);
ImGuiToolkit::Icon(s.icon().x, s.icon().y);
ImGui::SameLine(0, 10);
ImGui::Text("Rendering Output");
if ( ImGui::Button(IMGUI_TITLE_PREVIEW, ImVec2(IMGUI_RIGHT_ALIGN, 0)) )
@@ -345,11 +499,81 @@ void ImGuiVisitor::visit (RenderSource& s)
void ImGuiVisitor::visit (CloneSource& s)
{
ImGuiToolkit::Icon(9,2);
ImGuiToolkit::Icon(s.icon().x, s.icon().y);
ImGui::SameLine(0, 10);
ImGui::Text("Clone of %s", s.origin()->name().c_str());
std::string label = "Select " + s.origin()->name();
if ( ImGui::Button(label.c_str(), ImVec2(IMGUI_RIGHT_ALIGN, 0)) )
ImGui::Text("Clone");
if ( ImGui::Button(s.origin()->name().c_str(), ImVec2(IMGUI_RIGHT_ALIGN, 0)) )
Mixer::manager().setCurrentSource(s.origin());
}
void ImGuiVisitor::visit (PatternSource& s)
{
ImGuiToolkit::Icon(s.icon().x, s.icon().y);
ImGui::SameLine(0, 10);
ImGui::Text("Pattern");
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
if (ImGui::BeginCombo("##Patterns", Pattern::pattern_types[s.pattern()->type()].c_str()) )
{
for (uint p = 0; p < Pattern::pattern_types.size(); ++p){
if (ImGui::Selectable( Pattern::pattern_types[p].c_str() )) {
s.setPattern(p, s.pattern()->resolution());
std::ostringstream oss;
oss << s.name() << ": Pattern " << Pattern::pattern_types[p];
Action::manager().store(oss.str(), s.id());
}
}
ImGui::EndCombo();
}
}
void ImGuiVisitor::visit (DeviceSource& s)
{
ImGuiToolkit::Icon(s.icon().x, s.icon().y);
ImGui::SameLine(0, 10);
ImGui::Text("Device");
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
if (ImGui::BeginCombo("##Hardware", s.device().c_str()))
{
for (int d = 0; d < Device::manager().numDevices(); ++d){
std::string namedev = Device::manager().name(d);
if (ImGui::Selectable( namedev.c_str() )) {
s.setDevice(namedev);
std::ostringstream oss;
oss << s.name() << " Device " << namedev;
Action::manager().store(oss.str(), s.id());
}
}
ImGui::EndCombo();
}
DeviceConfigSet confs = Device::manager().config( Device::manager().index(s.device().c_str()));
if ( !confs.empty()) {
DeviceConfig best = *confs.rbegin();
float fps = static_cast<float>(best.fps_numerator) / static_cast<float>(best.fps_denominator);
ImGui::Text("%s %s %dx%d@%.1ffps", best.stream.c_str(), best.format.c_str(), best.width, best.height, fps);
}
}
void ImGuiVisitor::visit (NetworkSource& s)
{
ImGuiToolkit::Icon(s.icon().x, s.icon().y);
ImGui::SameLine(0, 10);
ImGui::Text("Network stream");
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_STREAM, 0.9f));
ImGui::Text("%s", s.connection().c_str());
ImGui::PopStyleColor(1);
NetworkStream *ns = s.networkStream();
ImGui::Text(" - %s (%dx%d)\n - Server address %s", NetworkToolkit::protocol_name[ns->protocol()],
ns->resolution().x, ns->resolution().y, ns->serverAddress().c_str());
if ( ImGui::Button( ICON_FA_REPLY " Reconnect", ImVec2(IMGUI_RIGHT_ALIGN, 0)) )
{
// TODO : reload ?
s.setConnection(s.connection());
}
}

View File

@@ -9,24 +9,26 @@ public:
ImGuiVisitor();
// Elements of Scene
void visit(Scene& n) override;
void visit(Node& n) override;
void visit(Group& n) override;
void visit(Switch& n) override;
void visit(Primitive& n) override;
void visit(MediaSurface& n) override;
void visit(FrameBufferSurface& n) override;
void visit (Scene& n) override;
void visit (Node& n) override;
void visit (Group& n) override;
void visit (Switch& n) override;
void visit (Primitive& n) override;
void visit (MediaSurface& n) override;
void visit (FrameBufferSurface& n) override;
// Elements with attributes
void visit(MediaPlayer& n) override;
void visit(Shader& n) override;
void visit(ImageShader& n) override;
void visit(ImageProcessingShader& n) override;
void visit (MediaPlayer& n) override;
void visit (Shader& n) override;
void visit (ImageProcessingShader& n) override;
void visit (Source& s) override;
void visit (MediaSource& s) override;
void visit (SessionSource& s) override;
void visit (RenderSource& s) override;
void visit (CloneSource& s) override;
void visit (PatternSource& s) override;
void visit (DeviceSource& s) override;
void visit (NetworkSource& s) override;
};
#endif // IMGUIVISITOR_H

View File

@@ -9,13 +9,13 @@ const char* ImageProcessingShader::filter_names[12] = { "None", "Blur", "Sharpen
"Erosion 3x3", "Erosion 5x5", "Erosion 7x7", "Dilation 3x3", "Dilation 5x5", "Dilation 7x7" };
ImageProcessingShader::ImageProcessingShader()
ImageProcessingShader::ImageProcessingShader(): Shader()
{
program_ = &imageProcessingShadingProgram;
reset();
}
ImageProcessingShader::ImageProcessingShader(const ImageProcessingShader &S)
ImageProcessingShader::ImageProcessingShader(const ImageProcessingShader &S): Shader()
{
program_ = &imageProcessingShadingProgram;
reset();
@@ -38,8 +38,6 @@ void ImageProcessingShader::use()
{
Shader::use();
// program_->setUniform("iChannelResolution[0]", iChannelResolution[0].x, iChannelResolution[0].y, iChannelResolution[0].z);
program_->setUniform("brightness", brightness);
program_->setUniform("contrast", contrast);
program_->setUniform("saturation", saturation);
@@ -63,9 +61,6 @@ void ImageProcessingShader::reset()
{
Shader::reset();
// // no texture resolution yet
// iChannelResolution[0] = glm::vec3(1.f);
// default values for image processing
brightness = 0.f;
contrast = 0.f;

View File

@@ -3,7 +3,7 @@
#include <glm/glm.hpp>
#include "Shader.h"
#include "ImageShader.h"
class ImageProcessingShader : public Shader
@@ -19,9 +19,6 @@ public:
void operator = (const ImageProcessingShader &S);
// // textures resolution
// glm::vec3 iChannelResolution[1];
// color effects
float brightness; // [-1 1]
float contrast; // [-1 1]

View File

@@ -4,27 +4,17 @@
#include "Visitor.h"
#include "ImageShader.h"
#include "Resource.h"
#include "rsc/fonts/IconsFontAwesome5.h"
//#include
static ShadingProgram imageShadingProgram("shaders/image.vs", "shaders/image.fs");
const char* ImageShader::mask_names[11] = { "None", "Glow", "Halo", "Circle", "Round", "Vignette", "Top", "Botton", "Left", "Right", "Custom" };
std::vector< uint > ImageShader::mask_presets;
const char* MaskShader::mask_names[3] = { ICON_FA_EXPAND, ICON_FA_EDIT, ICON_FA_SHAPES };
const char* MaskShader::mask_shapes[5] = { "Elipse", "Oblong", "Rectangle", "Horizontal", "Vertical" };
std::vector< ShadingProgram* > MaskShader::mask_programs;
ImageShader::ImageShader(): Shader(), custom_textureindex(0)
ImageShader::ImageShader(): Shader(), stipple(0.0)
{
// first initialization
if ( mask_presets.empty() ) {
mask_presets.push_back(Resource::getTextureWhite());
mask_presets.push_back(Resource::getTextureImage("images/mask_glow.png"));
mask_presets.push_back(Resource::getTextureImage("images/mask_halo.png"));
mask_presets.push_back(Resource::getTextureImage("images/mask_circle.png"));
mask_presets.push_back(Resource::getTextureImage("images/mask_roundcorner.png"));
mask_presets.push_back(Resource::getTextureImage("images/mask_vignette.png"));
mask_presets.push_back(Resource::getTextureImage("images/mask_linear_top.png"));
mask_presets.push_back(Resource::getTextureImage("images/mask_linear_bottom.png"));
mask_presets.push_back(Resource::getTextureImage("images/mask_linear_left.png"));
mask_presets.push_back(Resource::getTextureImage("images/mask_linear_right.png"));
}
// static program shader
program_ = &imageShadingProgram;
// reset instance
@@ -38,16 +28,10 @@ void ImageShader::use()
program_->setUniform("stipple", stipple);
glActiveTexture(GL_TEXTURE1);
if ( mask < 10 ) {
glBindTexture(GL_TEXTURE_2D, mask_presets[mask]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, mask_texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
else
glBindTexture(GL_TEXTURE_2D, custom_textureindex);
glActiveTexture(GL_TEXTURE0);
}
@@ -57,18 +41,16 @@ void ImageShader::reset()
Shader::reset();
// default mask
mask = 0;
custom_textureindex = mask_presets[0];
mask_texture = Resource::getTextureWhite();
// no stippling
stipple = 0.f;
}
void ImageShader::operator = (const ImageShader &S )
void ImageShader::operator = (const ImageShader &S)
{
Shader::operator =(S);
mask = S.mask;
custom_textureindex = S.custom_textureindex;
mask_texture = S.mask_texture;
stipple = S.stipple;
}
@@ -77,3 +59,83 @@ void ImageShader::accept(Visitor& v) {
Shader::accept(v);
v.visit(*this);
}
MaskShader::MaskShader(): Shader(), mode(0)
{
// first initialization
if ( mask_programs.empty() ) {
mask_programs.push_back(new ShadingProgram("shaders/simple.vs", "shaders/simple.fs"));
mask_programs.push_back(new ShadingProgram("shaders/image.vs", "shaders/mask_draw.fs"));
mask_programs.push_back(new ShadingProgram("shaders/simple.vs", "shaders/mask_elipse.fs"));
mask_programs.push_back(new ShadingProgram("shaders/simple.vs", "shaders/mask_round.fs"));
mask_programs.push_back(new ShadingProgram("shaders/simple.vs", "shaders/mask_box.fs"));
mask_programs.push_back(new ShadingProgram("shaders/simple.vs", "shaders/mask_horizontal.fs"));
mask_programs.push_back(new ShadingProgram("shaders/simple.vs", "shaders/mask_vertical.fs"));
}
// reset instance
reset();
// static program shader
program_ = mask_programs[0];
}
void MaskShader::use()
{
// select program to use
mode = CLAMP(mode, 0, 2);
shape = CLAMP(shape, 0, 4);
program_ = mode < 2 ? mask_programs[mode] : mask_programs[shape+2] ;
// actual use of shader program
Shader::use();
// shape parameters
size = shape < HORIZONTAL ? glm::max(glm::abs(size), glm::vec2(0.2)) : size;
program_->setUniform("size", size);
program_->setUniform("blur", blur);
// brush parameters
program_->setUniform("cursor", cursor);
program_->setUniform("brush", brush);
program_->setUniform("option", option);
program_->setUniform("effect", effect);
}
void MaskShader::reset()
{
Shader::reset();
// default mask
mode = 0;
// default shape
shape = 0;
blur = 0.5f;
size = glm::vec2(1.f, 1.f);
// default brush
cursor = glm::vec4(-10.f, -10.f, 1.f, 1.f);
brush = glm::vec3(0.5f, 0.1f, 0.f);
option = 0;
effect = 0;
}
void MaskShader::operator = (const MaskShader &S)
{
Shader::operator =(S);
mode = S.mode;
shape = S.shape;
blur = S.blur;
size = S.size;
}
void MaskShader::accept(Visitor& v) {
Shader::accept(v);
v.visit(*this);
}

View File

@@ -1,4 +1,4 @@
#ifndef IMAGESHADER_H
#ifndef IMAGESHADER_H
#define IMAGESHADER_H
#include <string>
@@ -14,22 +14,59 @@ class ImageShader : public Shader
{
public:
ImageShader();
// virtual ~ImageShader() {}
void use() override;
void reset() override;
void accept(Visitor& v) override;
void operator = (const ImageShader &S);
uint mask;
uint custom_textureindex;
float stipple;
uint mask_texture;
static const char* mask_names[11];
static std::vector< uint > mask_presets;
// uniforms
float stipple;
};
class MaskShader : public Shader
{
public:
MaskShader();
void use() override;
void reset() override;
void accept(Visitor& v) override;
void operator = (const MaskShader &S);
enum Modes {
NONE = 0,
PAINT = 1,
SHAPE = 2
};
uint mode;
enum Shapes {
ELIPSE = 0,
OBLONG = 1,
RECTANGLE = 2,
HORIZONTAL = 3,
VERTICAL = 4
};
uint shape;
// uniforms
glm::vec2 size;
float blur;
int option;
int effect;
glm::vec4 cursor;
glm::vec3 brush;
static const char* mask_names[3];
static const char* mask_shapes[5];
static std::vector< ShadingProgram* > mask_programs;
};
#endif // IMAGESHADER_H

241
Loopback.cpp Normal file
View File

@@ -0,0 +1,241 @@
#include <thread>
// Desktop OpenGL function loader
#include <glad/glad.h>
// gstreamer
#include <gst/gstformat.h>
#include <gst/video/video.h>
#include "defines.h"
#include "Settings.h"
#include "GstToolkit.h"
#include "SystemToolkit.h"
#include "FrameBuffer.h"
#include "Log.h"
#include "Loopback.h"
bool Loopback::system_loopback_initialized = false;
#if defined(LINUX)
/**
*
* Linux video 4 linux loopback device
*
* 1) Linux system has to have the v4l2loopback package
* See documentation at https://github.com/umlaeute/v4l2loopback
*
* $ sudo -A apt install v4l2loopback-dkms
*
* 2) User (sudo) has to install a v4l2loopback
*
* $ sudo -A modprobe v4l2loopback exclusive_caps=1 video_nr=10
*
* 3) But to do that, the user has to enter sudo passwd
*
* The command line above should be preceeded by
* export SUDO_ASKPASS="/tmp/mysudo.sh"
*
* where mysudo.sh contains the following:
* #!/bin/bash
* zenity --password --title=Authentication
*
* 4) Optionaly, we can set the dynamic properties of the stream
*
* $ sudo v4l2loopback-ctl set-caps "RGBA:640x480" /dev/video10
* $ sudo v4l2loopback-ctl set-fps 30 /dev/video10
*
* 5) Finally, the gstreamer pipeline can write into v4l2sink
*
* gst-launch-1.0 videotestsrc ! v4l2sink device=/dev/video10
*
*
* Useful command lines for debug
* $ v4l2-ctl --all -d 10
* $ gst-launch-1.0 v4l2src device=/dev/video10 ! videoconvert ! autovideosink
* $ gst-launch-1.0 videotestsrc ! v4l2sink device=/dev/video10
*/
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
std::string Loopback::system_loopback_name = "/dev/video10";
std::string Loopback::system_loopback_pipeline = "appsrc name=src ! videoconvert ! videorate ! video/x-raw,framerate=30/1 ! v4l2sink sync=false name=sink";
bool Loopback::initializeSystemLoopback()
{
if (!Loopback::systemLoopbackInitialized()) {
// create script for asking sudo password
std::string sudoscript = SystemToolkit::full_filename(SystemToolkit::settings_path(), "sudo.sh");
FILE *file = fopen(sudoscript.c_str(), "w");
if (file) {
fprintf(file, "#!/bin/bash\n");
fprintf(file, "zenity --password --title=Authentication\n");
fclose(file);
// make script executable
int fildes = 0;
fildes = open(sudoscript.c_str(), O_RDWR);
fchmod(fildes, S_IRWXU | S_IRWXG | S_IROTH | S_IWOTH);
close(fildes);
// create command line for installing v4l2loopback
std::string cmdline = "export SUDO_ASKPASS=\"" + sudoscript + "\"\n";
cmdline += "sudo -A apt install v4l2loopback-dkms 2>&1\n";
cmdline += "sudo -A modprobe -r v4l2loopback 2>&1\n";
cmdline += "sudo -A modprobe v4l2loopback exclusive_caps=1 video_nr=10 card_label=\"vimix loopback\" 2>&1\n";
// execute v4l2 command line
std::string report;
FILE *fp = popen(cmdline.c_str(), "r");
if (fp != NULL) {
// get stdout content from command line
char linestdout[PATH_MAX];
while (fgets(linestdout, PATH_MAX, fp) != NULL)
report += linestdout;
// error reported by pclose?
if (pclose(fp) != 0 )
Log::Warning("Failed to initialize system v4l2loopback\n%s", report.c_str());
// okay, probaly all good...
else
system_loopback_initialized = true;
}
else
Log::Warning("Failed to initialize system v4l2loopback\nCannot execute command line");
}
else
Log::Warning("Failed to initialize system v4l2loopback\nCannot create script", sudoscript.c_str());
}
return system_loopback_initialized;
}
bool Loopback::systemLoopbackInitialized()
{
// test if already initialized
if (!system_loopback_initialized) {
// check the existence of loopback device
if ( SystemToolkit::file_exists(system_loopback_name) )
system_loopback_initialized = true;
}
return system_loopback_initialized;
}
#else
std::string Loopback::system_loopback_name = "undefined";
std::string Loopback::system_loopback_pipeline = "";
bool Loopback::initializeSystemLoopback()
{
system_loopback_initialized = false;
return false;
}
bool Loopback::systemLoopbackInitialized()
{
return false;
}
#endif
Loopback::Loopback() : FrameGrabber()
{
frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, 60);
}
void Loopback::init(GstCaps *caps)
{
// ignore
if (caps == nullptr)
return;
if (!Loopback::systemLoopbackInitialized()){
Log::Warning("Loopback system shall be initialized first.");
finished_ = true;
return;
}
// create a gstreamer pipeline
std::string description = Loopback::system_loopback_pipeline;
// parse pipeline descriptor
GError *error = NULL;
pipeline_ = gst_parse_launch (description.c_str(), &error);
if (error != NULL) {
Log::Warning("Loopback Could not construct pipeline %s:\n%s", description.c_str(), error->message);
g_clear_error (&error);
finished_ = true;
return;
}
// setup device sink
g_object_set (G_OBJECT (gst_bin_get_by_name (GST_BIN (pipeline_), "sink")),
"device", Loopback::system_loopback_name.c_str(),
NULL);
// setup custom app source
src_ = GST_APP_SRC( gst_bin_get_by_name (GST_BIN (pipeline_), "src") );
if (src_) {
g_object_set (G_OBJECT (src_),
"stream-type", GST_APP_STREAM_TYPE_STREAM,
"is-live", TRUE,
"format", GST_FORMAT_TIME,
// "do-timestamp", TRUE,
NULL);
// Direct encoding (no buffering)
gst_app_src_set_max_bytes( src_, 0 );
// instruct src to use the required caps
caps_ = gst_caps_copy( caps );
gst_app_src_set_caps (src_, caps_);
// setup callbacks
GstAppSrcCallbacks callbacks;
callbacks.need_data = FrameGrabber::callback_need_data;
callbacks.enough_data = FrameGrabber::callback_enough_data;
callbacks.seek_data = NULL; // stream type is not seekable
gst_app_src_set_callbacks (src_, &callbacks, this, NULL);
}
else {
Log::Warning("Loopback Could not configure source");
finished_ = true;
return;
}
// start recording
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_PLAYING);
if (ret == GST_STATE_CHANGE_FAILURE) {
Log::Warning("Loopback Could not open %s", Loopback::system_loopback_name.c_str());
finished_ = true;
return;
}
// all good
#if defined(LINUX)
Log::Notify("Loopback started (v4l2loopback on %s)", Loopback::system_loopback_name.c_str());
#else
Log::Notify("Loopback started (%s)", Loopback::system_loopback_name.c_str());
#endif
// start
active_ = true;
}
void Loopback::terminate()
{
Log::Notify("Loopback to %s terminated.", Loopback::system_loopback_name.c_str());
}

31
Loopback.h Normal file
View File

@@ -0,0 +1,31 @@
#ifndef LOOPBACK_H
#define LOOPBACK_H
#include <vector>
#include <gst/pbutils/pbutils.h>
#include <gst/app/gstappsrc.h>
#include "FrameGrabber.h"
class Loopback : public FrameGrabber
{
static std::string system_loopback_pipeline;
static std::string system_loopback_name;
static bool system_loopback_initialized;
void init(GstCaps *caps) override;
void terminate() override;
public:
Loopback();
static bool systemLoopbackInitialized();
static bool initializeSystemLoopback();
};
#endif // LOOPBACK_H

View File

@@ -12,6 +12,8 @@ using namespace std;
#include "Resource.h"
#include "Visitor.h"
#include "SystemToolkit.h"
#include "GlmToolkit.h"
#include "GstToolkit.h"
#include "MediaPlayer.h"
@@ -26,12 +28,10 @@ std::list<MediaPlayer*> MediaPlayer::registered_;
MediaPlayer::MediaPlayer()
{
// create unique id
auto duration = std::chrono::high_resolution_clock::now().time_since_epoch();
id_ = std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count() % 1000000000;
id_ = GlmToolkit::uniqueId();
uri_ = "undefined";
pipeline_ = nullptr;
converter_ = nullptr;
ready_ = false;
failed_ = false;
@@ -76,7 +76,7 @@ guint MediaPlayer::texture() const
static MediaInfo UriDiscoverer_(std::string uri)
{
#ifdef MEDIA_PLAYER_DEBUG
Log::Info("Checking '%s'", uri.c_str());
Log::Info("Checking file '%s'", uri.c_str());
#endif
MediaInfo video_stream_info;
@@ -108,7 +108,7 @@ static MediaInfo UriDiscoverer_(std::string uri)
{
const GstStructure *s = gst_discoverer_info_get_misc (info);
gchar *str = gst_structure_to_string (s);
Log::Warning("'%s': Unknown file format %s", uri.c_str(), str);
Log::Warning("'%s': Unknown file format (%s)", uri.c_str(), str);
g_free (str);
}
break;
@@ -135,18 +135,20 @@ static MediaInfo UriDiscoverer_(std::string uri)
video_stream_info.interlaced = gst_discoverer_video_info_is_interlaced(vinfo);
video_stream_info.bitrate = gst_discoverer_video_info_get_bitrate(vinfo);
video_stream_info.isimage = gst_discoverer_video_info_is_image(vinfo);
// if its a video, it duration, framerate, etc.
// if its a video, set duration, framerate, etc.
if ( !video_stream_info.isimage ) {
video_stream_info.timeline.setEnd( gst_discoverer_info_get_duration (info) );
video_stream_info.seekable = gst_discoverer_info_get_seekable (info);
guint frn = gst_discoverer_video_info_get_framerate_num(vinfo);
guint frd = gst_discoverer_video_info_get_framerate_denom(vinfo);
if (frn == 0 || frd == 0) {
frn = 25;
frd = 1;
video_stream_info.framerate_n = gst_discoverer_video_info_get_framerate_num(vinfo);
video_stream_info.framerate_d = gst_discoverer_video_info_get_framerate_denom(vinfo);
if (video_stream_info.framerate_n == 0 || video_stream_info.framerate_d == 0) {
video_stream_info.framerate_n = 25;
video_stream_info.framerate_d = 1;
}
video_stream_info.framerate = static_cast<double>(frn) / static_cast<double>(frd);
video_stream_info.timeline.setStep( (GST_SECOND * static_cast<guint64>(frd)) / (static_cast<guint64>(frn)) );
video_stream_info.timeline.setStep( (GST_SECOND * static_cast<guint64>(video_stream_info.framerate_d)) / (static_cast<guint64>(video_stream_info.framerate_n)) );
// confirm (or infirm) that its not a single frame
if ( video_stream_info.timeline.numFrames() < 2)
video_stream_info.isimage = true;
}
// try to fill-in the codec information
GstCaps *caps = gst_discoverer_stream_info_get_caps (tmpinf);
@@ -171,14 +173,13 @@ static MediaInfo UriDiscoverer_(std::string uri)
gst_discoverer_stream_info_list_free(streams);
if (!video_stream_info.valid) {
Log::Warning("Warning: No video stream in '%s'", uri.c_str());
Log::Warning("'%s': No video stream", uri.c_str());
}
}
g_object_unref (discoverer);
}
// return the info
return video_stream_info;
}
@@ -186,12 +187,10 @@ static MediaInfo UriDiscoverer_(std::string uri)
void MediaPlayer::open(string path)
{
// set path
filename_ = path;
filename_ = SystemToolkit::transliterate( path );
// set uri to open
gchar *uritmp = gst_filename_to_uri(path.c_str(), NULL);
uri_ = string( uritmp );
g_free(uritmp);
uri_ = GstToolkit::filename_to_uri(path);
// reset
ready_ = false;
@@ -205,11 +204,11 @@ void MediaPlayer::open(string path)
void MediaPlayer::execute_open()
{
// Create the simplest gstreamer pipeline possible :
// Create gstreamer pipeline :
// " uridecodebin uri=file:///path_to_file/filename.mp4 ! videoconvert ! appsink "
// equivalent to gst-launch-1.0 uridecodebin uri=file:///path_to_file/filename.mp4 ! videoconvert ! ximagesink
string description = "uridecodebin uri=" + uri_ + " ! ";
// Build string describing pipeline
// video deinterlacing method
// tomsmocomp (0) Motion Adaptive: Motion Search
// greedyh (1) Motion Adaptive: Advanced Detection
@@ -217,15 +216,25 @@ void MediaPlayer::execute_open()
// vfir (3) Blur Vertical
// linear (4) Linear
// scalerbob (6) Double lines
if (media_.interlaced)
description += "deinterlace method=2 ! ";
// video convertion chroma-resampler
// Duplicates the samples when upsampling and drops when downsampling 0
// Uses linear interpolation 1 (default)
// Uses cubic interpolation 2
// Uses sinc interpolation 3
string description = "uridecodebin uri=" + uri_ + " ! ";
if (media_.interlaced)
description += "deinterlace method=2 ! ";
description += "videoconvert chroma-resampler=2 n-threads=2 ! appsink name=sink";
description += "videoconvert chroma-resampler=2 n-threads=2 ! ";
// hack to compensate for lack of PTS in gif animations
if (media_.codec_name.compare("image/gst-libav-gif") == 0){
description += "videorate ! video/x-raw,framerate=";
description += std::to_string(media_.framerate_n) + "/";
description += std::to_string(media_.framerate_d) + " ! ";
}
// set app sink
description += "appsink name=sink";
// parse pipeline descriptor
GError *error = NULL;
@@ -311,12 +320,11 @@ void MediaPlayer::execute_open()
media_.timeline.setEnd(d);
}
// all good
Log::Info("MediaPlayer %d Opened '%s' (%s %d x %d)", id_,
Log::Info("MediaPlayer %s Opened '%s' (%s %d x %d)", std::to_string(id_).c_str(),
uri_.c_str(), media_.codec_name.c_str(), media_.width, media_.height);
Log::Info("MediaPlayer %d Timeline [%ld %ld] %ld frames, %d gaps", id_,
Log::Info("MediaPlayer %s Timeline [%ld %ld] %ld frames, %d gaps", std::to_string(id_).c_str(),
media_.timeline.begin(), media_.timeline.end(), media_.timeline.numFrames(), media_.timeline.numGaps());
ready_ = true;
@@ -348,10 +356,6 @@ void MediaPlayer::close()
// un-ready the media player
ready_ = false;
// no more need to reference converter
if (converter_ != nullptr)
gst_object_unref (converter_);
// clean up GST
if (pipeline_ != nullptr) {
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_NULL);
@@ -381,6 +385,10 @@ void MediaPlayer::close()
glDeleteBuffers(2, pbo_);
pbo_size_ = 0;
#ifdef MEDIA_PLAYER_DEBUG
Log::Info("MediaPlayer %s closed", std::to_string(id_).c_str());
#endif
// unregister media player
MediaPlayer::registered_.remove(this);
}
@@ -432,7 +440,7 @@ void MediaPlayer::enable(bool on)
// apply state change
GstStateChangeReturn ret = gst_element_set_state (pipeline_, requested_state);
if (ret == GST_STATE_CHANGE_FAILURE) {
Log::Warning("MediaPlayer %s Failed to enable", gst_element_get_name(pipeline_));
Log::Warning("MediaPlayer %s Failed to enable", std::to_string(id_).c_str());
failed_ = true;
}
@@ -479,14 +487,14 @@ void MediaPlayer::play(bool on)
// all ready, apply state change immediately
GstStateChangeReturn ret = gst_element_set_state (pipeline_, desired_state_);
if (ret == GST_STATE_CHANGE_FAILURE) {
Log::Warning("MediaPlayer %s Failed to play", gst_element_get_name(pipeline_));
Log::Warning("MediaPlayer %s Failed to play", std::to_string(id_).c_str());
failed_ = true;
}
#ifdef MEDIA_PLAYER_DEBUG
else if (on)
Log::Info("MediaPlayer %s Start", gst_element_get_name(pipeline_));
Log::Info("MediaPlayer %s Start", std::to_string(id_).c_str());
else
Log::Info("MediaPlayer %s Stop [%ld]", gst_element_get_name(pipeline_), position());
Log::Info("MediaPlayer %s Stop [%ld]", std::to_string(id_).c_str(), position());
#endif
// reset time counter
@@ -608,6 +616,8 @@ void MediaPlayer::init_texture(guint index)
GL_RGBA, GL_UNSIGNED_BYTE, frame_[index].vframe.data[0]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
if (!media_.isimage) {
@@ -634,7 +644,7 @@ void MediaPlayer::init_texture(guint index)
}
else {
// did not work, disable PBO
glDeleteBuffers(4, pbo_);
glDeleteBuffers(2, pbo_);
pbo_[0] = pbo_[1] = 0;
pbo_size_ = 0;
break;
@@ -717,6 +727,10 @@ void MediaPlayer::update()
// if its ok, open the media
if (media_.valid)
execute_open();
else {
Log::Warning("MediaPlayer %s Loading cancelled", std::to_string(id_).c_str());
failed_ = true;
}
}
// wait next frame to display
return;
@@ -734,13 +748,13 @@ void MediaPlayer::update()
index_lock_.lock();
// get the last frame filled from fill_frame()
read_index = last_index_;
// Do NOT miss and jump directly (after seek) to a pre-roll
for (guint i = 0; i < N_VFRAME; ++i) {
if (frame_[i].status == PREROLL) {
read_index = i;
break;
}
}
// // Do NOT miss and jump directly (after seek) to a pre-roll
// for (guint i = 0; i < N_VFRAME; ++i) {
// if (frame_[i].status == PREROLL) {
// read_index = i;
// break;
// }
// }
// unlock access to index change
index_lock_.unlock();
@@ -764,7 +778,7 @@ void MediaPlayer::update()
fill_texture(read_index);
// double update for pre-roll frame and dual PBO (ensure frame is displayed now)
if (frame_[read_index].status == PREROLL && pbo_size_ > 0)
if ( (frame_[read_index].status == PREROLL || seeking_ ) && pbo_size_ > 0)
fill_texture(read_index);
}
@@ -810,12 +824,12 @@ void MediaPlayer::update()
}
}
// manage loop mode
if (need_loop) {
execute_loop_command();
}
}
// manage loop mode
if (need_loop) {
execute_loop_command();
}
}
void MediaPlayer::execute_loop_command()
@@ -873,7 +887,7 @@ void MediaPlayer::execute_seek_command(GstClockTime target)
else {
seeking_ = true;
#ifdef MEDIA_PLAYER_DEBUG
Log::Info("MediaPlayer %s Seek %ld %f", std::to_string(id_).c_str(), seek_pos, rate_);
Log::Info("MediaPlayer %s Seek %ld %.1f", std::to_string(id_).c_str(), seek_pos, rate_);
#endif
}
@@ -936,7 +950,7 @@ std::string MediaPlayer::filename() const
double MediaPlayer::frameRate() const
{
return media_.framerate;
return static_cast<double>(media_.framerate_n) / static_cast<double>(media_.framerate_d);;
}
double MediaPlayer::updateFrameRate() const

View File

@@ -27,7 +27,8 @@ struct MediaInfo {
guint par_width; // width to match pixel aspect ratio
guint height;
guint bitrate;
gdouble framerate;
guint framerate_n;
guint framerate_d;
std::string codec_name;
bool isimage;
bool interlaced;
@@ -38,7 +39,8 @@ struct MediaInfo {
width = par_width = 640;
height = 480;
bitrate = 0;
framerate = 0.0;
framerate_n = 1;
framerate_d = 25;
codec_name = "unknown";
isimage = false;
interlaced = false;
@@ -56,7 +58,8 @@ struct MediaInfo {
this->par_width = b.par_width;
this->height = b.height;
this->bitrate = b.bitrate;
this->framerate = b.framerate;
this->framerate_n = b.framerate_n;
this->framerate_d = b.framerate_d;
this->codec_name = b.codec_name;
this->valid = b.valid;
this->isimage = b.isimage;
@@ -79,6 +82,10 @@ public:
* Destructor.
*/
~MediaPlayer();
/**
* Get unique id
*/
inline uint64_t id() const { return id_; }
/**
* Open a media using gstreamer URI
* */
@@ -247,7 +254,7 @@ public:
private:
// video player description
int id_;
uint64_t id_;
std::string filename_;
std::string uri_;
guint textureindex_;
@@ -262,7 +269,6 @@ private:
LoopMode loop_;
GstState desired_state_;
GstElement *pipeline_;
GstElement *converter_;
GstVideoInfo v_frame_video_info_;
std::atomic<bool> ready_;
std::atomic<bool> failed_;

View File

@@ -6,7 +6,7 @@
#include "ImageShader.h"
#include "ImageProcessingShader.h"
#include "Resource.h"
#include "Primitives.h"
#include "Decorations.h"
#include "MediaPlayer.h"
#include "Visitor.h"
#include "Log.h"
@@ -15,29 +15,21 @@ MediaSource::MediaSource() : Source(), path_("")
{
// create media player
mediaplayer_ = new MediaPlayer;
// create media surface:
// - textured with original texture from media player
// - crop & repeat UV can be managed here
// - additional custom shader can be associated
mediasurface_ = new Surface(renderingshader_);
}
MediaSource::~MediaSource()
{
// delete media surface & player
delete mediasurface_;
// delete media player
delete mediaplayer_;
}
void MediaSource::setPath(const std::string &p)
{
Log::Notify("Creating Source with media '%s'", p.c_str());
path_ = p;
mediaplayer_->open(path_);
mediaplayer_->play(true);
Log::Notify("Opening %s", p.c_str());
}
std::string MediaSource::path() const
@@ -50,6 +42,14 @@ MediaPlayer *MediaSource::mediaplayer() const
return mediaplayer_;
}
glm::ivec2 MediaSource::icon() const
{
if (mediaplayer_->isImage())
return glm::ivec2(2, 9);
else
return glm::ivec2(18, 13);
}
bool MediaSource::failed() const
{
return mediaplayer_->failed();
@@ -60,11 +60,6 @@ uint MediaSource::texture() const
return mediaplayer_->texture();
}
void MediaSource::replaceRenderingShader()
{
mediasurface_->replaceShader(renderingshader_);
}
void MediaSource::init()
{
if ( mediaplayer_->isOpen() ) {
@@ -76,32 +71,30 @@ void MediaSource::init()
if (mediaplayer_->texture() != Resource::getTextureBlack()) {
// get the texture index from media player, apply it to the media surface
mediasurface_->setTextureIndex( mediaplayer_->texture() );
texturesurface_->setTextureIndex( mediaplayer_->texture() );
// create Frame buffer matching size of media player
float height = float(mediaplayer()->width()) / mediaplayer()->aspectRatio();
FrameBuffer *renderbuffer = new FrameBuffer(mediaplayer()->width(), (uint)height, true);
float height = float(mediaplayer_->width()) / mediaplayer_->aspectRatio();
FrameBuffer *renderbuffer = new FrameBuffer(mediaplayer_->width(), (uint)height, true);
// icon in mixing view
if (mediaplayer_->isImage())
symbol_ = new Symbol(Symbol::IMAGE, glm::vec3(0.8f, 0.8f, 0.01f));
else
symbol_ = new Symbol(Symbol::VIDEO, glm::vec3(0.8f, 0.8f, 0.01f));
// set the renderbuffer of the source and attach rendering nodes
attach(renderbuffer);
// icon in mixing view
if (mediaplayer_->isImage()) {
overlays_[View::MIXING]->attach( new Symbol(Symbol::IMAGE, glm::vec3(0.8f, 0.8f, 0.01f)) );
overlays_[View::LAYER]->attach( new Symbol(Symbol::IMAGE, glm::vec3(0.8f, 0.8f, 0.01f)) );
}
else {
overlays_[View::MIXING]->attach( new Symbol(Symbol::VIDEO, glm::vec3(0.8f, 0.8f, 0.01f)) );
overlays_[View::LAYER]->attach( new Symbol(Symbol::VIDEO, glm::vec3(0.8f, 0.8f, 0.01f)) );
}
// force update of activation mode
active_ = true;
// deep update to reorder
View::need_deep_update_++;
// done init
initialized_ = true;
Log::Info("Source Media linked to Media %s.", mediaplayer()->uri().c_str());
// force update of activation mode
active_ = true;
touch();
Log::Info("Source '%s' linked to Media %s.", name().c_str(), std::to_string(mediaplayer_->id()).c_str());
}
}
@@ -115,7 +108,7 @@ void MediaSource::setActive (bool on)
// change status of media player (only if status changed)
if ( active_ != was_active ) {
mediaplayer()->enable(active_);
mediaplayer_->enable(active_);
}
}
@@ -132,11 +125,17 @@ void MediaSource::render()
if (!initialized_)
init();
else {
// blendingshader_->color.r = mediaplayer_->currentTimelineFading();
// blendingshader_->color.g = mediaplayer_->currentTimelineFading();
// blendingshader_->color.b = mediaplayer_->currentTimelineFading();
// render the media player into frame buffer
static glm::mat4 projection = glm::ortho(-1.f, 1.f, 1.f, -1.f, -1.f, 1.f);
renderbuffer_->begin();
mediasurface_->shader()->color.a = mediaplayer_->currentTimelineFading();
mediasurface_->draw(glm::identity<glm::mat4>(), projection);
// texturesurface_->shader()->color.a = mediaplayer_->currentTimelineFading();
texturesurface_->shader()->color.r = mediaplayer_->currentTimelineFading();
texturesurface_->shader()->color.g = mediaplayer_->currentTimelineFading();
texturesurface_->shader()->color.b = mediaplayer_->currentTimelineFading();
texturesurface_->draw(glm::identity<glm::mat4>(), renderbuffer_->projection());
renderbuffer_->end();
}
}

View File

@@ -3,6 +3,8 @@
#include "Source.h"
class MediaPlayer;
class MediaSource : public Source
{
public:
@@ -22,12 +24,12 @@ public:
std::string path() const;
MediaPlayer *mediaplayer() const;
glm::ivec2 icon() const override;
protected:
void init() override;
void replaceRenderingShader() override;
Surface *mediasurface_;
std::string path_;
MediaPlayer *mediaplayer_;
};

385
Mixer.cpp
View File

@@ -18,10 +18,16 @@ using namespace tinyxml2;
#include "Log.h"
#include "View.h"
#include "SystemToolkit.h"
//#include "GarbageVisitor.h"
#include "SessionCreator.h"
#include "SessionVisitor.h"
#include "SessionSource.h"
#include "MediaSource.h"
#include "PatternSource.h"
#include "DeviceSource.h"
#include "StreamSource.h"
#include "NetworkSource.h"
#include "ActionManager.h"
#include "Streamer.h"
#include "Mixer.h"
@@ -40,23 +46,21 @@ static void saveSession(const std::string& filename, Session *session)
// creation of XML doc
XMLDocument xmlDoc;
XMLElement *version = xmlDoc.NewElement(APP_NAME);
version->SetAttribute("major", XML_VERSION_MAJOR);
version->SetAttribute("minor", XML_VERSION_MINOR);
version->SetAttribute("size", session->numSource());
version->SetAttribute("date", SystemToolkit::date_time_string().c_str());
xmlDoc.InsertEndChild(version);
XMLElement *rootnode = xmlDoc.NewElement(APP_NAME);
rootnode->SetAttribute("major", XML_VERSION_MAJOR);
rootnode->SetAttribute("minor", XML_VERSION_MINOR);
rootnode->SetAttribute("size", session->numSource());
rootnode->SetAttribute("date", SystemToolkit::date_time_string().c_str());
rootnode->SetAttribute("resolution", session->frame()->info().c_str());
xmlDoc.InsertEndChild(rootnode);
// 1. list of sources
XMLElement *sessionNode = xmlDoc.NewElement("Session");
xmlDoc.InsertEndChild(sessionNode);
SourceList::iterator iter;
for (iter = session->begin(); iter != session->end(); iter++)
{
SessionVisitor sv(&xmlDoc, sessionNode);
SessionVisitor sv(&xmlDoc, sessionNode);
for (auto iter = session->begin(); iter != session->end(); iter++, sv.setRoot(sessionNode) )
// source visitor
(*iter)->accept(sv);
}
// 2. config of views
XMLElement *views = xmlDoc.NewElement("Views");
@@ -74,6 +78,10 @@ static void saveSession(const std::string& filename, Session *session)
layer->InsertEndChild( SessionVisitor::NodeToXML(*session->config(View::LAYER), &xmlDoc));
views->InsertEndChild(layer);
XMLElement *appearance = xmlDoc.NewElement( "Appearance" );
appearance->InsertEndChild( SessionVisitor::NodeToXML(*session->config(View::APPEARANCE), &xmlDoc));
views->InsertEndChild(appearance);
XMLElement *render = xmlDoc.NewElement( "Rendering" );
render->InsertEndChild( SessionVisitor::NodeToXML(*session->config(View::RENDERING), &xmlDoc));
views->InsertEndChild(render);
@@ -109,8 +117,9 @@ Mixer::Mixer() : session_(nullptr), back_session_(nullptr), current_view_(nullpt
// auto load if Settings ask to
if ( Settings::application.recentSessions.load_at_start &&
Settings::application.recentSessions.valid_file &&
Settings::application.recentSessions.filenames.size() > 0 )
Settings::application.recentSessions.front_is_valid &&
Settings::application.recentSessions.filenames.size() > 0 &&
Settings::application.fresh_start)
load( Settings::application.recentSessions.filenames.front() );
else
// initializes with a new empty session
@@ -186,17 +195,25 @@ void Mixer::update()
session_->update(dt_);
// delete sources which failed update (one by one)
if (session()->failedSource() != nullptr)
deleteSource(session()->failedSource());
Source *failure = session()->failedSource();
if (failure != nullptr) {
MediaSource *failedFile = dynamic_cast<MediaSource *>(failure);
if (failedFile != nullptr) {
Settings::application.recentImport.remove( failedFile->path() );
}
deleteSource(failure, false);
}
// update views
mixing_.update(dt_);
geometry_.update(dt_);
layer_.update(dt_);
appearance_.update(dt_);
transition_.update(dt_);
// deep updates shall be performed only 1 frame
View::need_deep_update_ = false;
// deep update was performed
if (View::need_deep_update_ > 0)
View::need_deep_update_--;
}
void Mixer::draw()
@@ -207,7 +224,7 @@ void Mixer::draw()
}
// manangement of sources
Source * Mixer::createSourceFile(std::string path)
Source * Mixer::createSourceFile(const std::string &path)
{
// ready to create a source
Source *s = nullptr;
@@ -236,11 +253,13 @@ Source * Mixer::createSourceFile(std::string path)
Settings::application.recentImport.path = SystemToolkit::path_filename(path);
// propose a new name based on uri
renameSource(s, SystemToolkit::base_filename(path));
s->setName(SystemToolkit::base_filename(path));
}
else
else {
Settings::application.recentImport.remove(path);
Log::Notify("File %s does not exist.", path.c_str());
}
return s;
}
@@ -251,12 +270,64 @@ Source * Mixer::createSourceRender()
RenderSource *s = new RenderSource(session_);
// propose a new name based on session name
renameSource(s, SystemToolkit::base_filename(session_->filename()));
s->setName(SystemToolkit::base_filename(session_->filename()));
return s;
}
Source * Mixer::createSourceClone(std::string namesource)
Source * Mixer::createSourceStream(const std::string &gstreamerpipeline)
{
// ready to create a source
GenericStreamSource *s = new GenericStreamSource;
s->setDescription(gstreamerpipeline);
// propose a new name based on pattern name
std::string name = gstreamerpipeline.substr(0, gstreamerpipeline.find(" "));
s->setName(name);
return s;
}
Source * Mixer::createSourcePattern(uint pattern, glm::ivec2 res)
{
// ready to create a source
PatternSource *s = new PatternSource;
s->setPattern(pattern, res);
// propose a new name based on pattern name
std::string name = Pattern::pattern_types[pattern];
name = name.substr(0, name.find(" "));
s->setName(name);
return s;
}
Source * Mixer::createSourceDevice(const std::string &namedevice)
{
// ready to create a source
Source *s = Device::manager().createSource(namedevice);
// propose a new name based on pattern name
std::string name = namedevice.substr(0, namedevice.find(" "));
s->setName(name);
return s;
}
Source * Mixer::createSourceNetwork(const std::string &nameconnection)
{
// ready to create a source
NetworkSource *s = new NetworkSource;
s->setConnection(nameconnection);
// propose a new name based on address
s->setName(nameconnection);
return s;
}
Source * Mixer::createSourceClone(const std::string &namesource)
{
// ready to create a source
Source *s = nullptr;
@@ -274,7 +345,7 @@ Source * Mixer::createSourceClone(std::string namesource)
// create a source
s = (*origin)->clone();
// get new name
// propose new name (this automatically increments name)
renameSource(s, (*origin)->name());
}
@@ -283,15 +354,19 @@ Source * Mixer::createSourceClone(std::string namesource)
void Mixer::addSource(Source *s)
{
if (s != nullptr)
if (s != nullptr) {
candidate_sources_.push_back(s);
}
}
void Mixer::insertSource(Source *s, View::Mode m)
{
if ( s != nullptr )
{
// Add source to Session
// avoid duplicate name
renameSource(s, s->name());
// Add source to Session (ignored if source already in)
SourceList::iterator sit = session_->addSource(s);
// set a default depth to the new source
@@ -301,9 +376,10 @@ void Mixer::insertSource(Source *s, View::Mode m)
mixing_.setAlpha(s);
// add sources Nodes to all views
mixing_.scene.ws()->attach(s->group(View::MIXING));
geometry_.scene.ws()->attach(s->group(View::GEOMETRY));
layer_.scene.ws()->attach(s->group(View::LAYER));
attach(s);
// new state in history manager
Action::manager().store(s->name() + std::string(" inserted"), s->id());
// if requested to show the source in a given view
// (known to work for View::MIXING et TRANSITION: other views untested)
@@ -320,28 +396,24 @@ void Mixer::insertSource(Source *s, View::Mode m)
}
}
void Mixer::deleteSource(Source *s)
void Mixer::deleteSource(Source *s, bool withundo)
{
if ( s != nullptr )
{
// in case it was the current source...
unsetCurrentSource();
// in case it was selected..
selection().remove(s);
// keep name for log
std::string name = s->name();
uint64_t id = s->id();
// remove source Nodes from all views
mixing_.scene.ws()->detatch( s->group(View::MIXING) );
geometry_.scene.ws()->detatch( s->group(View::GEOMETRY) );
layer_.scene.ws()->detatch( s->group(View::LAYER) );
transition_.scene.ws()->detatch( s->group(View::TRANSITION) );
detach(s);
// delete source
session_->deleteSource(s);
// store new state in history manager
if (withundo)
Action::manager().store(name + std::string(" deleted"), id);
// log
Log::Notify("Source %s deleted.", name.c_str());
}
@@ -356,10 +428,95 @@ void Mixer::deleteSource(Source *s)
}
void Mixer::attach(Source *s)
{
if ( s != nullptr )
{
// force update
s->touch();
// attach to views
mixing_.scene.ws()->attach( s->group(View::MIXING) );
geometry_.scene.ws()->attach( s->group(View::GEOMETRY) );
layer_.scene.ws()->attach( s->group(View::LAYER) );
appearance_.scene.ws()->attach( s->group(View::APPEARANCE) );
}
}
void Mixer::detach(Source *s)
{
if ( s != nullptr )
{
// in case it was the current source...
unsetCurrentSource();
// in case it was selected..
selection().remove(s);
// detach from views
mixing_.scene.ws()->detach( s->group(View::MIXING) );
geometry_.scene.ws()->detach( s->group(View::GEOMETRY) );
layer_.scene.ws()->detach( s->group(View::LAYER) );
appearance_.scene.ws()->detach( s->group(View::APPEARANCE) );
transition_.scene.ws()->detach( s->group(View::TRANSITION) );
}
}
bool Mixer::concealed(Source *s)
{
SourceList::iterator it = std::find(stash_.begin(), stash_.end(), s);
return it != stash_.end();
}
void Mixer::conceal(Source *s)
{
if ( !concealed(s) ) {
// in case it was the current source...
unsetCurrentSource();
// in case it was selected..
selection().remove(s);
// store to stash
stash_.push_front(s);
// remove from session
session_->removeSource(s);
// detach from scene workspace, and put only in mixing background
detach(s);
mixing_.scene.bg()->attach( s->group(View::MIXING) );
}
}
void Mixer::uncover(Source *s)
{
SourceList::iterator it = std::find(stash_.begin(), stash_.end(), s);
if ( it != stash_.end() ) {
stash_.erase(it);
mixing_.scene.bg()->detach( s->group(View::MIXING) );
attach(s);
session_->addSource(s);
}
}
void Mixer::deleteSelection()
{
// get clones first : this way we store the history of deletion in the right order
SourceList selection_clones_;
for ( auto sit = selection().begin(); sit != selection().end(); sit++ ) {
CloneSource *clone = dynamic_cast<CloneSource *>(*sit);
if (clone)
selection_clones_.push_back(clone);
}
// delete all clones
while ( !selection_clones_.empty() ) {
deleteSource( selection_clones_.front());
selection_clones_.pop_front();
}
// empty the selection
while ( !selection().empty() )
deleteSource( selection().front());
deleteSource( selection().front()); // this also remove element from selection()
}
@@ -374,15 +531,12 @@ void Mixer::renameSource(Source *s, const std::string &newname)
if ( newname.empty() )
tentativename = "source";
// trivial case : same name as current
if ( tentativename == s->name() )
return;
// search for a source of the name 'tentativename'
std::string basename = tentativename;
int count = 1;
while ( std::find_if(session_->begin(), session_->end(), Source::hasName(tentativename)) != session_->end() ) {
tentativename = basename + std::to_string(++count);
for( auto it = session_->begin(); it != session_->end(); it++){
if ( s->id() != (*it)->id() && (*it)->name() == tentativename )
tentativename = basename + std::to_string( ++count );
}
// ok to rename
@@ -396,10 +550,10 @@ void Mixer::setCurrentSource(SourceList::iterator it)
if ( current_source_ == it )
return;
// clear current (even if it is invalid)
// clear current (even if 'it' is invalid)
unsetCurrentSource();
// change current if it is valid
// change current if 'it' is valid
if ( it != session_->end() ) {
current_source_ = it;
current_source_index_ = session_->index(current_source_);
@@ -411,8 +565,11 @@ void Mixer::setCurrentSource(SourceList::iterator it)
// show status as current
(*current_source_)->setMode(Source::CURRENT);
(*current_source_)->group(View::MIXING)->update_callbacks_.push_back(new BounceScaleCallback);
(*current_source_)->group(View::LAYER)->update_callbacks_.push_back(new BounceScaleCallback);
if (current_view_ == &mixing_)
(*current_source_)->group(View::MIXING)->update_callbacks_.push_back(new BounceScaleCallback);
else if (current_view_ == &layer_)
(*current_source_)->group(View::LAYER)->update_callbacks_.push_back(new BounceScaleCallback);
}
}
@@ -425,6 +582,7 @@ Source * Mixer::findSource (Node *node)
return nullptr;
}
Source * Mixer::findSource (std::string namesource)
{
SourceList::iterator it = session_->find(namesource);
@@ -433,9 +591,24 @@ Source * Mixer::findSource (std::string namesource)
return nullptr;
}
Source * Mixer::findSource (uint64_t id)
{
SourceList::iterator it = session_->find(id);
if (it != session_->end())
return *it;
return nullptr;
}
void Mixer::setCurrentSource(uint64_t id)
{
setCurrentSource( session_->find(id) );
}
void Mixer::setCurrentSource(Node *node)
{
setCurrentSource( session_->find(node) );
if (node!=nullptr)
setCurrentSource( session_->find(node) );
}
void Mixer::setCurrentSource(std::string namesource)
@@ -445,25 +618,43 @@ void Mixer::setCurrentSource(std::string namesource)
void Mixer::setCurrentSource(Source *s)
{
setCurrentSource( session_->find(s) );
if (s!=nullptr)
setCurrentSource( session_->find(s) );
}
void Mixer::setCurrentSource(int index)
void Mixer::setCurrentIndex(int index)
{
setCurrentSource( session_->find(index) );
setCurrentSource( session_->at(index) );
}
void Mixer::setCurrentNext()
{
SourceList::iterator it = current_source_;
if (session_->numSource() > 0) {
it++;
SourceList::iterator it = current_source_;
it++;
if (it == session_->end()) {
it = session_->begin();
if (it == session_->end()) {
it = session_->begin();
}
setCurrentSource( it );
}
}
setCurrentSource( it );
void Mixer::setCurrentPrevious()
{
if (session_->numSource() > 0) {
SourceList::iterator it = current_source_;
if (it == session_->begin()) {
it = session_->end();
}
it--;
setCurrentSource( it );
}
}
void Mixer::unsetCurrentSource()
@@ -522,6 +713,9 @@ void Mixer::setView(View::Mode m)
case View::LAYER:
current_view_ = &layer_;
break;
case View::APPEARANCE:
current_view_ = &appearance_;
break;
case View::MIXING:
default:
current_view_ = &mixing_;
@@ -529,7 +723,7 @@ void Mixer::setView(View::Mode m)
}
// need to deeply update view to apply eventual changes
View::need_deep_update_ = true;
View::need_deep_update_++;
Settings::application.current_view = (int) m;
}
@@ -543,6 +737,8 @@ View *Mixer::view(View::Mode m)
return &geometry_;
case View::LAYER:
return &layer_;
case View::APPEARANCE:
return &appearance_;
case View::MIXING:
return &mixing_;
default:
@@ -562,6 +758,7 @@ void Mixer::saveas(const std::string& filename)
session_->config(View::MIXING)->copyTransform( mixing_.scene.root() );
session_->config(View::GEOMETRY)->copyTransform( geometry_.scene.root() );
session_->config(View::LAYER)->copyTransform( layer_.scene.root() );
session_->config(View::APPEARANCE)->copyTransform( appearance_.scene.root() );
// launch a thread to save the session
std::thread (saveSession, filename, session_).detach();
@@ -569,16 +766,18 @@ void Mixer::saveas(const std::string& filename)
void Mixer::load(const std::string& filename)
{
if (filename.empty())
return;
#ifdef THREADED_LOADING
// load only one at a time
if (sessionLoaders_.empty()) {
// Start async thread for loading the session
// Will be obtained in the future in update()
sessionLoaders_.emplace_back( std::async(std::launch::async, loadSession_, filename) );
sessionLoaders_.emplace_back( std::async(std::launch::async, Session::load, filename) );
}
#else
set( loadSession_(filename) );
set( Session::load(filename) );
#endif
}
@@ -613,10 +812,10 @@ void Mixer::import(const std::string& filename)
if (sessionImporters_.empty()) {
// Start async thread for loading the session
// Will be obtained in the future in update()
sessionImporters_.emplace_back( std::async(std::launch::async, loadSession_, filename) );
sessionImporters_.emplace_back( std::async(std::launch::async, Session::load, filename) );
}
#else
merge( loadSession_(filename) );
merge( Session::load(filename) );
#endif
}
@@ -628,8 +827,10 @@ void Mixer::merge(Session *session)
}
// import every sources
for ( Source *s = session->popSource(); s != nullptr; s = session->popSource())
for ( Source *s = session->popSource(); s != nullptr; s = session->popSource()) {
renameSource(s, s->name());
insertSource(s);
}
}
void Mixer::swap()
@@ -642,12 +843,7 @@ void Mixer::swap()
selection().clear();
// detatch current session's nodes from views
for (auto source_iter = session_->begin(); source_iter != session_->end(); source_iter++)
{
mixing_.scene.ws()->detatch( (*source_iter)->group(View::MIXING) );
geometry_.scene.ws()->detatch( (*source_iter)->group(View::GEOMETRY) );
layer_.scene.ws()->detatch( (*source_iter)->group(View::LAYER) );
transition_.scene.ws()->detatch( (*source_iter)->group(View::TRANSITION) );
}
detach(*source_iter);
}
// swap back and front
@@ -655,21 +851,15 @@ void Mixer::swap()
session_ = back_session_;
back_session_ = tmp;
// swap recorders
back_session_->transferRecorders(session_);
// attach new session's nodes to views
for (auto source_iter = session_->begin(); source_iter != session_->end(); source_iter++)
{
mixing_.scene.ws()->attach( (*source_iter)->group(View::MIXING) );
geometry_.scene.ws()->attach( (*source_iter)->group(View::GEOMETRY) );
layer_.scene.ws()->attach( (*source_iter)->group(View::LAYER) );
}
attach(*source_iter);
// optional copy of views config
mixing_.scene.root()->copyTransform( session_->config(View::MIXING) );
geometry_.scene.root()->copyTransform( session_->config(View::GEOMETRY) );
layer_.scene.root()->copyTransform( session_->config(View::LAYER) );
appearance_.scene.root()->copyTransform( session_->config(View::APPEARANCE) );
// set resolution
session_->setResolution( session_->config(View::RENDERING)->scale_ );
@@ -677,9 +867,6 @@ void Mixer::swap()
// transfer fading
session_->setFading( MAX(back_session_->fading(), session_->fading()), true );
// request complete update for views
View::need_deep_update_ = true;
// no current source
current_source_ = session_->end();
current_source_index_ = -1;
@@ -691,6 +878,9 @@ void Mixer::swap()
garbage_.push_back(back_session_);
back_session_ = nullptr;
// reset History manager
Action::manager().clear();
// notification
Log::Notify("Session %s loaded. %d source(s) created.", session_->filename().c_str(), session_->numSource());
}
@@ -725,13 +915,16 @@ void Mixer::clear()
// swap current with empty
sessionSwapRequested_ = true;
// need to deeply update view to apply eventual changes
View::need_deep_update_++;
Log::Info("New session ready.");
}
void Mixer::set(Session *s)
{
if ( s == nullptr ) {
Log::Warning("Failed to load Session.");
Log::Warning("Session loading cancelled.");
return;
}
@@ -745,3 +938,27 @@ void Mixer::set(Session *s)
// swap current with given session
sessionSwapRequested_ = true;
}
void Mixer::paste(const std::string& clipboard)
{
if (clipboard.empty())
return;
tinyxml2::XMLDocument xmlDoc;
tinyxml2::XMLError eResult = xmlDoc.Parse(clipboard.c_str());
if ( XMLResultError(eResult))
return;
tinyxml2::XMLElement *root = xmlDoc.FirstChildElement(APP_NAME);
if ( root == nullptr )
return;
SessionLoader loader( session_ );
XMLElement* sourceNode = root->FirstChildElement("Source");
for( ; sourceNode ; sourceNode = sourceNode->NextSiblingElement())
{
addSource(loader.cloneOrCreateSource(sourceNode));
}
}

29
Mixer.h
View File

@@ -36,34 +36,48 @@ public:
void draw();
// creation of sources
Source * createSourceFile (std::string path);
Source * createSourceClone (std::string namesource = "");
Source * createSourceFile (const std::string &path);
Source * createSourceClone (const std::string &namesource = "");
Source * createSourceRender ();
Source * createSourceStream (const std::string &gstreamerpipeline);
Source * createSourcePattern(uint pattern, glm::ivec2 res);
Source * createSourceDevice (const std::string &namedevice);
Source * createSourceNetwork(const std::string &nameconnection);
// operations on sources
void addSource (Source *s);
void deleteSource (Source *s);
void deleteSource (Source *s, bool withundo=true);
void renameSource (Source *s, const std::string &newname);
void attach (Source *s);
void detach (Source *s);
void deleteSelection();
// current source
Source * currentSource ();
void setCurrentSource (Source *s);
void setCurrentSource (std::string namesource);
void setCurrentSource (Node *node);
void setCurrentSource (int index);
void setCurrentSource (uint64_t id);
void setCurrentNext ();
void setCurrentPrevious ();
void unsetCurrentSource ();
void setCurrentIndex (int index);
int indexCurrentSource ();
Source * currentSource ();
// browsing into sources
Source * findSource (Node *node);
Source * findSource (std::string name);
Source * findSource (uint64_t id);
// management of view
View *view (View::Mode m = View::INVALID);
void setView (View::Mode m);
void conceal(Source *s);
void uncover(Source *s);
bool concealed(Source *s);
// manipulate, load and save sessions
inline Session *session () const { return session_; }
void clear ();
@@ -78,6 +92,9 @@ public:
void close ();
void open (const std::string& filename);
// create sources if clipboard contains well-formed xml text
void paste (const std::string& clipboard);
protected:
Session *session_;
@@ -87,6 +104,7 @@ protected:
void swap();
SourceList candidate_sources_;
SourceList stash_;
void insertSource(Source *s, View::Mode m = View::INVALID);
void setCurrentSource(SourceList::iterator it);
@@ -97,6 +115,7 @@ protected:
MixingView mixing_;
GeometryView geometry_;
LayerView layer_;
AppearanceView appearance_;
TransitionView transition_;
guint64 update_time_;

316
NetworkSource.cpp Normal file
View File

@@ -0,0 +1,316 @@
#include <algorithm>
#include <sstream>
#include <thread>
#include <chrono>
#include <future>
#include <glm/gtc/matrix_transform.hpp>
#include <gst/pbutils/pbutils.h>
#include <gst/gst.h>
#include "SystemToolkit.h"
#include "defines.h"
#include "Stream.h"
#include "Decorations.h"
#include "Visitor.h"
#include "Log.h"
#include "Connection.h"
#include "NetworkSource.h"
#ifndef NDEBUG
#define NETWORK_DEBUG
#endif
// this is called when receiving an answer for streaming request
void StreamerResponseListener::ProcessMessage( const osc::ReceivedMessage& m,
const IpEndpointName& remoteEndpoint )
{
char sender[IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH];
remoteEndpoint.AddressAndPortAsString(sender);
try{
if( std::strcmp( m.AddressPattern(), OSC_PREFIX OSC_STREAM_OFFER ) == 0 ){
#ifdef NETWORK_DEBUG
Log::Info("Received stream info from %s", sender);
#endif
NetworkToolkit::StreamConfig conf;
// someone is offering a stream
osc::ReceivedMessage::const_iterator arg = m.ArgumentsBegin();
conf.port = (arg++)->AsInt32();
conf.protocol = (NetworkToolkit::Protocol) (arg++)->AsInt32();
conf.width = (arg++)->AsInt32();
conf.height = (arg++)->AsInt32();
// we got the offer from Streaming::manager()
parent_->config_ = conf;
parent_->connected_ = true;
parent_->received_config_ = true;
}
else if( std::strcmp( m.AddressPattern(), OSC_PREFIX OSC_STREAM_REJECT ) == 0 ){
#ifdef NETWORK_DEBUG
Log::Info("Received rejection from %s", sender);
#endif
parent_->connected_ = false;
parent_->received_config_ = true;
}
}
catch( osc::Exception& e ){
// any parsing errors such as unexpected argument types, or
// missing arguments get thrown as exceptions.
Log::Info("error while parsing message '%s' from %s : %s", m.AddressPattern(), sender, e.what());
}
}
NetworkStream::NetworkStream(): Stream(), receiver_(nullptr)
{
received_config_ = false;
connected_ = false;
}
glm::ivec2 NetworkStream::resolution() const
{
return glm::ivec2(config_.width, config_.height);
}
std::string NetworkStream::clientAddress() const
{
return config_.client_address + ":" + std::to_string(config_.port);
}
std::string NetworkStream::serverAddress() const
{
return streamer_.address;
}
void wait_for_stream_(UdpListeningReceiveSocket *receiver)
{
receiver->Run();
}
void NetworkStream::connect(const std::string &nameconnection)
{
// start fresh
if (connected())
disconnect();
received_config_ = false;
// refuse self referencing
if (nameconnection.compare(Connection::manager().info().name) == 0) {
Log::Warning("Cannot create self-referencing Network Source '%s'", nameconnection.c_str());
failed_ = true;
return;
}
// does this Connection exists?
int streamer_index = Connection::manager().index(nameconnection);
// Nope, cannot connect to unknown connection
if (streamer_index < 0) {
Log::Warning("Cannot connect to %s: please make sure %s is active on this machine.", nameconnection.c_str(), APP_NAME);
failed_ = true;
return;
}
// ok, we want to ask to this connected streamer to send us a stream
streamer_ = Connection::manager().info(streamer_index);
std::string listener_address = NetworkToolkit::closest_host_ip(streamer_.address);
// prepare listener to receive stream config from remote streaming manager
listener_.setParent(this);
// find an available port to receive response from remote streaming manager
int listener_port_ = -1;
for (int trial = 0; receiver_ == nullptr && trial < 10 ; trial++) {
try {
// invent a port which would be available
listener_port_ = 72000 + rand()%1000;
// try to create receiver (through exception on fail)
receiver_ = new UdpListeningReceiveSocket(IpEndpointName(listener_address.c_str(), listener_port_), &listener_);
}
catch (const std::runtime_error&) {
receiver_ = nullptr;
}
}
if (receiver_ == nullptr) {
Log::Notify("Cannot establish connection with %s. Please check your network.", streamer_.name.c_str());
failed_ = true;
return;
}
// build OSC message
char buffer[IP_MTU_SIZE];
osc::OutboundPacketStream p( buffer, IP_MTU_SIZE );
p.Clear();
p << osc::BeginMessage( OSC_PREFIX OSC_STREAM_REQUEST );
// send my listening port to indicate to Connection::manager where to reply
p << listener_port_;
p << Connection::manager().info().name.c_str();
p << osc::EndMessage;
// send OSC message to streamer
UdpTransmitSocket socket( IpEndpointName(streamer_.address.c_str(), streamer_.port_stream_request) );
socket.Send( p.Data(), p.Size() );
// Now we wait for the offer from the streamer
std::thread(wait_for_stream_, receiver_).detach();
#ifdef NETWORK_DEBUG
Log::Info("Asking %s:%d for a stream", streamer_.address.c_str(), streamer_.port_stream_request);
Log::Info("Waiting for response at %s:%d", Connection::manager().info().address.c_str(), listener_port_);
#endif
}
void NetworkStream::disconnect()
{
// receiver should not be active anyway, make sure it is deleted
if (receiver_) {
delete receiver_;
receiver_ = nullptr;
}
if (connected_) {
// build OSC message to inform disconnection
char buffer[IP_MTU_SIZE];
osc::OutboundPacketStream p( buffer, IP_MTU_SIZE );
p.Clear();
p << osc::BeginMessage( OSC_PREFIX OSC_STREAM_DISCONNECT );
p << config_.port; // send my stream port to identify myself to the streamer Connection::manager
p << osc::EndMessage;
// send OSC message to streamer
UdpTransmitSocket socket( IpEndpointName(streamer_.address.c_str(), streamer_.port_stream_request) );
socket.Send( p.Data(), p.Size() );
connected_ = false;
}
close();
}
bool NetworkStream::connected() const
{
return connected_ && Stream::isPlaying();
}
void NetworkStream::update()
{
Stream::update();
if ( !ready_ && !failed_ && received_config_)
{
// only once
received_config_ = false;
// stop receiving streamer info
if (receiver_)
receiver_->AsynchronousBreak();
if (connected_) {
#ifdef NETWORK_DEBUG
Log::Info("Creating Network Stream %d (%d x %d)", config_.port, config_.width, config_.height);
#endif
// prepare pipeline parameter with port given in config_
std::string parameter = std::to_string(config_.port);
// make sure the shared memory socket exists
if (config_.protocol == NetworkToolkit::SHM_RAW) {
// for shared memory, the parameter is a file location in settings
parameter = SystemToolkit::full_filename(SystemToolkit::temp_path(), "shm") + parameter;
// try few times to see if file exists and wait 20ms each time
for(int trial = 0; trial < 5; trial ++){
if ( SystemToolkit::file_exists(parameter))
break;
std::this_thread::sleep_for (std::chrono::milliseconds(20));
}
// failed to find the shm socket file: cannot connect
if (!SystemToolkit::file_exists(parameter)) {
Log::Warning("Cannot connect to shared memory %s.", parameter.c_str());
failed_ = true;
}
parameter = "\"" + parameter + "\"";
}
// general case : create pipeline and open
if (!failed_) {
// build the pipeline depending on stream info
std::ostringstream pipeline;
// get generic pipeline string
std::string pipelinestring = NetworkToolkit::protocol_receive_pipeline[config_.protocol];
// find placeholder for PORT
int xxxx = pipelinestring.find("XXXX");
// keep beginning of pipeline
pipeline << pipelinestring.substr(0, xxxx);
// Replace 'XXXX' by info on port config
pipeline << parameter;
// keep ending of pipeline
pipeline << pipelinestring.substr(xxxx + 4);
// add a videoconverter
pipeline << " ! videoconvert";
// open the pipeline with generic stream class
Stream::open(pipeline.str(), config_.width, config_.height);
}
}
else {
Log::Warning("Connection was rejected by %s.\nMake sure it accepts connection and try again.", streamer_.name.c_str());
failed_=true;
}
}
}
NetworkSource::NetworkSource() : StreamSource()
{
// create stream
stream_ = (Stream *) new NetworkStream;
// set symbol
symbol_ = new Symbol(Symbol::SHARE, glm::vec3(0.8f, 0.8f, 0.01f));
}
NetworkSource::~NetworkSource()
{
networkStream()->disconnect();
}
NetworkStream *NetworkSource::networkStream() const
{
return dynamic_cast<NetworkStream *>(stream_);
}
void NetworkSource::setConnection(const std::string &nameconnection)
{
connection_name_ = nameconnection;
Log::Notify("Network Source connecting to '%s'", connection_name_.c_str());
// open network stream
networkStream()->connect( connection_name_ );
stream_->play(true);
}
std::string NetworkSource::connection() const
{
return connection_name_;
}
void NetworkSource::accept(Visitor& v)
{
Source::accept(v);
if (!failed())
v.visit(*this);
}

82
NetworkSource.h Normal file
View File

@@ -0,0 +1,82 @@
#ifndef NETWORKSOURCE_H
#define NETWORKSOURCE_H
#include "osc/OscReceivedElements.h"
#include "osc/OscPacketListener.h"
#include "osc/OscOutboundPacketStream.h"
#include "ip/UdpSocket.h"
#include "NetworkToolkit.h"
#include "Connection.h"
#include "StreamSource.h"
class NetworkStream;
class StreamerResponseListener : public osc::OscPacketListener
{
protected:
class NetworkStream *parent_;
virtual void ProcessMessage( const osc::ReceivedMessage& m,
const IpEndpointName& remoteEndpoint );
public:
inline void setParent(NetworkStream *s) { parent_ = s; }
};
class NetworkStream : public Stream
{
friend class StreamerResponseListener;
public:
NetworkStream();
void connect(const std::string &nameconnection);
bool connected() const;
void disconnect();
void update() override;
glm::ivec2 resolution() const;
inline NetworkToolkit::Protocol protocol() const { return config_.protocol; }
std::string clientAddress() const;
std::string serverAddress() const;
private:
// connection information
ConnectionInfo streamer_;
StreamerResponseListener listener_;
UdpListeningReceiveSocket *receiver_;
std::atomic<bool> received_config_;
std::atomic<bool> connected_;
NetworkToolkit::StreamConfig config_;
};
class NetworkSource : public StreamSource
{
std::string connection_name_;
public:
NetworkSource();
~NetworkSource();
// Source interface
void accept (Visitor& v) override;
// StreamSource interface
Stream *stream() const override { return stream_; }
NetworkStream *networkStream() const;
// specific interface
void setConnection(const std::string &nameconnection);
std::string connection() const;
glm::ivec2 icon() const override { return glm::ivec2(18, 11); }
};
#endif // NETWORKSOURCE_H

208
NetworkToolkit.cpp Normal file
View File

@@ -0,0 +1,208 @@
#include <algorithm>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <netdb.h>
#ifdef linux
#include <linux/netdevice.h>
#endif
// OSC IP gethostbyname
#include "ip/NetworkingUtils.h"
#include "NetworkToolkit.h"
/***
*
* TCP Server JPEG : broadcast
* SND:
* gst-launch-1.0 videotestsrc is-live=true ! jpegenc ! rtpjpegpay ! rtpstreampay ! tcpserversink port=5400
* RCV:
* gst-launch-1.0 tcpclientsrc port=5400 ! application/x-rtp-stream,encoding-name=JPEG ! rtpstreamdepay! rtpjpegdepay ! jpegdec ! autovideosink
*
* TCP Server H264 : broadcast
* SND:
* gst-launch-1.0 videotestsrc is-live=true ! x264enc ! rtph264pay ! rtpstreampay ! tcpserversink port=5400
* RCV:
* gst-launch-1.0 tcpclientsrc port=5400 ! application/x-rtp-stream,media=video,encoding-name=H264,payload=96,clock-rate=90000 ! rtpstreamdepay ! rtpjitterbuffer ! rtph264depay ! avdec_h264 ! autovideosink
*
* UDP unicast
* SND
* gst-launch-1.0 videotestsrc is-live=true ! videoconvert ! video/x-raw, format=RGB, framerate=30/1 ! queue max-size-buffers=3 ! jpegenc ! rtpjpegpay ! udpsink port=5000 host=127.0.0.1
* RCV
* gst-launch-1.0 udpsrc port=5000 ! application/x-rtp,encoding-name=JPEG,payload=26 ! rtpjpegdepay ! jpegdec ! autovideosink
*
* * UDP multicast : hass to know the PORT and IP of all clients
* SND
* gst-launch-1.0 videotestsrc is-live=true ! jpegenc ! rtpjpegpay ! multiudpsink clients="127.0.0.1:5000,127.0.0.1:5001"
* RCV
* gst-launch-1.0 -v udpsrc port=5000 ! application/x-rtp,encoding-name=JPEG,payload=26 ! rtpjpegdepay ! jpegdec ! autovideosink
* gst-launch-1.0 -v udpsrc port=5001 ! application/x-rtp,encoding-name=JPEG,payload=26 ! rtpjpegdepay ! jpegdec ! autovideosink
*
* RAW UDP (caps has to match exactly, and depends on resolution)
* SND
* gst-launch-1.0 -v videotestsrc is-live=true ! video/x-raw,format=RGBA,width=1920,height=1080 ! rtpvrawpay ! udpsink port=5000 host=127.0.0.1
* RCV
* gst-launch-1.0 udpsrc port=5000 caps = "application/x-rtp, media=(string)video, clock-rate=(int)90000, encoding-name=(string)RAW, sampling=(string)RGBA, depth=(string)8, width=(string)1920, height=(string)1080, colorimetry=(string)SMPTE240M, payload=(int)96, ssrc=(uint)2272750581, timestamp-offset=(uint)1699493959, seqnum-offset=(uint)14107, a-framerate=(string)30" ! rtpvrawdepay ! videoconvert ! autovideosink
*
*
* SHM RAW RGB
* SND
* gst-launch-1.0 videotestsrc is-live=true ! video/x-raw, format=RGB, framerate=30/1 ! shmsink socket-path=/tmp/blah
* RCV
* gst-launch-1.0 shmsrc is-live=true socket-path=/tmp/blah ! video/x-raw, format=RGB, framerate=30/1, width=320, height=240 ! videoconvert ! autovideosink
*
* */
const char* NetworkToolkit::protocol_name[NetworkToolkit::DEFAULT] = {
"Shared Memory",
"RTP JPEG Stream",
"RTP H264 Stream",
"RTP JPEG Broadcast",
"RTP H264 Broadcast"
};
const std::vector<std::string> NetworkToolkit::protocol_send_pipeline {
"video/x-raw, format=RGB, framerate=30/1 ! queue max-size-buffers=10 ! shmsink buffer-time=100000 wait-for-connection=true name=sink",
"video/x-raw, format=I420, framerate=30/1 ! queue max-size-buffers=10 ! jpegenc ! rtpjpegpay ! udpsink name=sink",
"video/x-raw, format=I420, framerate=30/1 ! queue max-size-buffers=10 ! x264enc tune=\"zerolatency\" threads=2 ! rtph264pay ! udpsink name=sink",
"video/x-raw, format=I420, framerate=30/1 ! queue max-size-buffers=3 ! jpegenc ! rtpjpegpay ! rtpstreampay ! tcpserversink name=sink",
"video/x-raw, format=I420, framerate=30/1 ! queue max-size-buffers=3 ! x264enc tune=\"zerolatency\" threads=2 ! rtph264pay ! rtpstreampay ! tcpserversink name=sink"
};
const std::vector<std::string> NetworkToolkit::protocol_receive_pipeline {
"shmsrc socket-path=XXXX ! video/x-raw, format=RGB, framerate=30/1 ! queue max-size-buffers=10",
"udpsrc buffer-size=200000 port=XXXX ! application/x-rtp,encoding-name=JPEG,payload=26,clock-rate=90000 ! queue max-size-buffers=10 ! rtpjpegdepay ! jpegdec",
"udpsrc buffer-size=200000 port=XXXX ! application/x-rtp,encoding-name=H264,payload=96,clock-rate=90000 ! queue ! rtph264depay ! avdec_h264",
"tcpclientsrc timeout=1 port=XXXX ! queue max-size-buffers=30 ! application/x-rtp-stream,media=video,encoding-name=JPEG,payload=26,clock-rate=90000 ! rtpstreamdepay ! rtpjpegdepay ! jpegdec",
"tcpclientsrc timeout=1 port=XXXX ! queue max-size-buffers=30 ! application/x-rtp-stream,media=video,encoding-name=H264,payload=96,clock-rate=90000 ! rtpstreamdepay ! rtph264depay ! avdec_h264"
};
bool initialized_ = false;
std::vector<std::string> ipstrings_;
std::vector<unsigned long> iplongs_;
void add_interface(int fd, const char *name) {
struct ifreq ifreq;
char host[128];
memset(&ifreq, 0, sizeof ifreq);
strncpy(ifreq.ifr_name, name, IFNAMSIZ);
if(ioctl(fd, SIOCGIFADDR, &ifreq)==0) {
int family;
switch(family=ifreq.ifr_addr.sa_family) {
case AF_INET:
case AF_INET6:
getnameinfo(&ifreq.ifr_addr, sizeof ifreq.ifr_addr, host, sizeof host, 0, 0, NI_NUMERICHOST);
break;
default:
case AF_UNSPEC:
return; /* ignore */
}
// add only if not already listed
std::string hostip(host);
if ( std::find(ipstrings_.begin(), ipstrings_.end(), hostip) == ipstrings_.end() )
{
ipstrings_.push_back( hostip );
iplongs_.push_back( GetHostByName(host) );
// printf("%s %s %lu\n", name, host, GetHostByName(host));
}
}
}
void list_interfaces()
{
struct ifreq *ifreq;
struct ifconf ifconf;
char buf[16384];
int fd=socket(PF_INET, SOCK_DGRAM, 0);
if(fd > -1) {
ifconf.ifc_len=sizeof buf;
ifconf.ifc_buf=buf;
if(ioctl(fd, SIOCGIFCONF, &ifconf)==0) {
ifreq=ifconf.ifc_req;
for(int i=0;i<ifconf.ifc_len;) {
size_t len;
#ifndef linux
len=IFNAMSIZ + ifreq->ifr_addr.sa_len;
#else
len=sizeof *ifreq;
#endif
add_interface(fd, ifreq->ifr_name);
ifreq=(struct ifreq*)((char*)ifreq+len);
i+=len;
}
}
}
close(fd);
initialized_ = true;
}
std::vector<std::string> NetworkToolkit::host_ips()
{
if (!initialized_)
list_interfaces();
return ipstrings_;
}
bool NetworkToolkit::is_host_ip(const std::string &ip)
{
if ( ip.compare("localhost") == 0)
return true;
if (!initialized_)
list_interfaces();
return std::find(ipstrings_.begin(), ipstrings_.end(), ip) != ipstrings_.end();
}
std::string NetworkToolkit::closest_host_ip(const std::string &ip)
{
std::string address = "localhost";
if (!initialized_)
list_interfaces();
// discard trivial case
if ( ip.compare("localhost") != 0)
{
int index_mini = -1;
unsigned long host = GetHostByName( ip.c_str() );
unsigned long mini = host;
for (size_t i=0; i < iplongs_.size(); i++){
unsigned long diff = host > iplongs_[i] ? host-iplongs_[i] : iplongs_[i]-host;
if (diff < mini) {
mini = diff;
index_mini = (int) i;
}
}
if (index_mini>0)
address = ipstrings_[index_mini];
}
return address;
}
std::string NetworkToolkit::hostname()
{
char hostname[1024];
hostname[1023] = '\0';
gethostname(hostname, 1023);
return std::string(hostname);
}

78
NetworkToolkit.h Normal file
View File

@@ -0,0 +1,78 @@
#ifndef NETWORKTOOLKIT_H
#define NETWORKTOOLKIT_H
#include <string>
#include <vector>
#define OSC_PREFIX "/vimix"
#define OSC_PING "/ping"
#define OSC_PONG "/pong"
#define OSC_STREAM_REQUEST "/request"
#define OSC_STREAM_OFFER "/offer"
#define OSC_STREAM_REJECT "/reject"
#define OSC_STREAM_DISCONNECT "/disconnect"
#define MAX_HANDSHAKE 20
#define HANDSHAKE_PORT 71310
#define STREAM_REQUEST_PORT 71510
#define OSC_DIALOG_PORT 71010
#define IP_MTU_SIZE 1536
namespace NetworkToolkit
{
typedef enum {
SHM_RAW = 0,
UDP_JPEG,
UDP_H264,
TCP_JPEG,
TCP_H264,
DEFAULT
} Protocol;
struct StreamConfig {
Protocol protocol;
std::string client_name;
std::string client_address;
int port;
int width;
int height;
StreamConfig () {
protocol = DEFAULT;
client_name = "";
client_address = "127.0.0.1";
port = 0;
width = 0;
height = 0;
}
inline StreamConfig& operator = (const StreamConfig& o)
{
if (this != &o) {
this->client_name = o.client_name;
this->client_address = o.client_address;
this->port = o.port;
this->protocol = o.protocol;
this->width = o.width;
this->height = o.height;
}
return *this;
}
};
extern const char* protocol_name[DEFAULT];
extern const std::vector<std::string> protocol_send_pipeline;
extern const std::vector<std::string> protocol_receive_pipeline;
std::string hostname();
std::vector<std::string> host_ips();
bool is_host_ip(const std::string &ip);
std::string closest_host_ip(const std::string &ip);
}
#endif // NETWORKTOOLKIT_H

163
PatternSource.cpp Normal file
View File

@@ -0,0 +1,163 @@
#include <sstream>
#include <glm/gtc/matrix_transform.hpp>
#include "PatternSource.h"
#include "defines.h"
#include "ImageShader.h"
#include "Resource.h"
#include "Decorations.h"
#include "Stream.h"
#include "Visitor.h"
#include "Log.h"
#define MAX_PATTERN 24
// smpte (0) SMPTE 100%% color bars
// snow (1) Random (television snow)
// black (2) 100%% Black
// white (3) 100%% White
// red (4) Red
// green (5) Green
// blue (6) Blue
// checkers-1 (7) Checkers 1px
// checkers-2 (8) Checkers 2px
// checkers-4 (9) Checkers 4px
// checkers-8 (10) Checkers 8px
// circular (11) Circular
// blink (12) Blink
// smpte75 (13) SMPTE 75%% color bars
// zone-plate (14) Zone plate
// gamut (15) Gamut checkers
// chroma-zone-plate (16) Chroma zone plate
// solid-color (17) Solid color
// ball (18) Moving ball
// smpte100 (19) SMPTE 100%% color bars
// bar (20) Bar
// pinwheel (21) Pinwheel
// spokes (22) Spokes
// gradient (23) Gradient
// colors (24) Colors
const char* pattern_internal_[MAX_PATTERN] = { "videotestsrc pattern=black",
"videotestsrc pattern=white",
"videotestsrc pattern=gradient",
"videotestsrc pattern=checkers-1 ! video/x-raw,format=GRAY8 ! videoconvert",
"videotestsrc pattern=checkers-8 ! video/x-raw,format=GRAY8 ! videoconvert",
"videotestsrc pattern=circular",
"frei0r-src-lissajous0r ratiox=0.001 ratioy=0.999 ! videoconvert",
"videotestsrc pattern=pinwheel",
"videotestsrc pattern=spokes",
"videotestsrc pattern=red",
"videotestsrc pattern=green",
"videotestsrc pattern=blue",
"videotestsrc pattern=smpte100",
"videotestsrc pattern=colors",
"videotestsrc pattern=smpte",
"videotestsrc pattern=snow",
"videotestsrc pattern=blink",
"videotestsrc pattern=zone-plate",
"videotestsrc pattern=chroma-zone-plate",
"videotestsrc pattern=bar horizontal-speed=5",
"videotestsrc pattern=ball",
"frei0r-src-ising0r",
"videotestsrc pattern=black ! timeoverlay halignment=center valignment=center font-desc=\"Sans, 72\" ",
"videotestsrc pattern=black ! clockoverlay halignment=center valignment=center font-desc=\"Sans, 72\" "
};
std::vector<std::string> Pattern::pattern_types = { "Black",
"White",
"Gradient",
"Checkers 1x1 px",
"Checkers 8x8 px",
"Circles",
"Lissajous",
"Pinwheel",
"Spokes",
"Red",
"Green",
"Blue",
"Color bars",
"RGB grid",
"SMPTE test pattern",
"Television snow",
"Blink",
"Fresnel zone plate",
"Chroma zone plate",
"Bar moving",
"Ball bouncing"
#if GST_VERSION_MINOR > 17
,
"Blob",
"Timer",
"Clock"
#endif
};
Pattern::Pattern() : Stream(), type_(MAX_PATTERN) // invalid pattern
{
}
glm::ivec2 Pattern::resolution()
{
return glm::ivec2( width_, height_);
}
void Pattern::open( uint pattern, glm::ivec2 res )
{
type_ = MIN(pattern, MAX_PATTERN-1);
std::string gstreamer_pattern = pattern_internal_[type_];
// there is always a special case...
switch(type_)
{
case 18: // zone plates
case 17:
{
std::ostringstream oss;
oss << " kx2=" << (int)(res.x * 10.f / res.y) << " ky2=10 kt=4";
gstreamer_pattern += oss.str(); // Zone plate
}
break;
default:
break;
}
// all patterns before 'SMPTE test pattern' are single frames (not animated)
single_frame_ = type_ < 14;
// (private) open stream
Stream::open(gstreamer_pattern, res.x, res.y);
}
PatternSource::PatternSource() : StreamSource()
{
// create stream
stream_ = (Stream *) new Pattern;
// set symbol
symbol_ = new Symbol(Symbol::PATTERN, glm::vec3(0.8f, 0.8f, 0.01f));
}
void PatternSource::setPattern(uint type, glm::ivec2 resolution)
{
Log::Notify("Creating Source with pattern '%s'", Pattern::pattern_types[type].c_str());
pattern()->open( (uint) type, resolution );
stream_->play(true);
}
void PatternSource::accept(Visitor& v)
{
Source::accept(v);
if (!failed())
v.visit(*this);
}
Pattern *PatternSource::pattern() const
{
return dynamic_cast<Pattern *>(stream_);
}

42
PatternSource.h Normal file
View File

@@ -0,0 +1,42 @@
#ifndef PATTERNSOURCE_H
#define PATTERNSOURCE_H
#include <vector>
#include "StreamSource.h"
class Pattern : public Stream
{
public:
static std::vector<std::string> pattern_types;
Pattern();
void open( uint pattern, glm::ivec2 res);
glm::ivec2 resolution();
inline uint type() const { return type_; }
private:
uint type_;
};
class PatternSource : public StreamSource
{
public:
PatternSource();
// Source interface
void accept (Visitor& v) override;
// StreamSource interface
Stream *stream() const override { return stream_; }
// specific interface
Pattern *pattern() const;
void setPattern(uint type, glm::ivec2 resolution);
glm::ivec2 icon() const override { return glm::ivec2(12, 5); }
};
#endif // PATTERNSOURCE_H

View File

@@ -1,7 +1,6 @@
#include "PickingVisitor.h"
#include "Log.h"
#include "Primitives.h"
#include "Decorations.h"
#include "GlmToolkit.h"
@@ -12,13 +11,13 @@
#include <glm/gtx/vector_angle.hpp>
PickingVisitor::PickingVisitor(glm::vec3 coordinates) : Visitor()
PickingVisitor::PickingVisitor(glm::vec3 coordinates, bool force) : Visitor(), force_(force)
{
modelview_ = glm::mat4(1.f);
points_.push_back( coordinates );
}
PickingVisitor::PickingVisitor(glm::vec3 selectionstart, glm::vec3 selection_end) : Visitor()
PickingVisitor::PickingVisitor(glm::vec3 selectionstart, glm::vec3 selection_end, bool force) : Visitor(), force_(force)
{
modelview_ = glm::mat4(1.f);
points_.push_back( selectionstart );
@@ -33,12 +32,12 @@ void PickingVisitor::visit(Node &n)
void PickingVisitor::visit(Group &n)
{
if (!n.visible_)
if (!n.visible_ && !force_)
return;
glm::mat4 mv = modelview_;
for (NodeSet::iterator node = n.begin(); node != n.end(); node++) {
if ( (*node)->visible_ )
if ( (*node)->visible_ || force_)
(*node)->accept(*this);
modelview_ = mv;
}
@@ -46,7 +45,7 @@ void PickingVisitor::visit(Group &n)
void PickingVisitor::visit(Switch &n)
{
if (!n.visible_ || n.numChildren()<1)
if ((!n.visible_ && !force_) || n.numChildren()<1)
return;
glm::mat4 mv = modelview_;
@@ -61,7 +60,7 @@ void PickingVisitor::visit(Primitive &)
void PickingVisitor::visit(Surface &n)
{
if (!n.visible_)
if (!n.visible_ && !force_)
return;
// if more than one point given for testing: test overlap
@@ -101,7 +100,7 @@ void PickingVisitor::visit(Surface &n)
void PickingVisitor::visit(Disk &n)
{
// discard if not visible or if not exactly one point given for picking
if (!n.visible_ || points_.size() != 1)
if ((!n.visible_ && !force_) || points_.size() != 1)
return;
// apply inverse transform to the point of interest
@@ -117,7 +116,7 @@ void PickingVisitor::visit(Disk &n)
void PickingVisitor::visit(Handles &n)
{
// discard if not visible or if not exactly one point given for picking
if (!n.visible_ || points_.size() != 1)
if ((!n.visible_ && !force_) || points_.size() != 1)
return;
// apply inverse transform to the point of interest
@@ -127,6 +126,13 @@ void PickingVisitor::visit(Handles &n)
glm::vec4 S = glm::inverse(modelview_) * glm::vec4( 0.05f, 0.05f, 0.f, 0.f );
float scale = glm::length( glm::vec2(S) );
// extract rotation from modelview
glm::mat4 ctm;
glm::vec3 rot(0.f);
glm::vec4 vec = modelview_ * glm::vec4(1.f, 0.f, 0.f, 0.f);
rot.z = glm::orientedAngle( glm::vec3(1.f, 0.f, 0.f), glm::normalize(glm::vec3(vec)), glm::vec3(0.f, 0.f, 1.f) );
ctm = glm::rotate(glm::identity<glm::mat4>(), -rot.z, glm::vec3(0.f, 0.f, 1.f)) * modelview_ ;
bool picked = false;
if ( n.type() == Handles::RESIZE ) {
// 4 corners
@@ -147,15 +153,23 @@ void PickingVisitor::visit(Handles &n)
}
else if ( n.type() == Handles::ROTATE ){
// the icon for rotation is on the right top corner at (0.12, 0.12) in scene coordinates
glm::vec4 vec = glm::inverse(modelview_) * glm::vec4( 0.1f, 0.1f, 0.f, 0.f );
float l = glm::length( glm::vec2(vec) );
picked = glm::length( glm::vec2( 1.f + l, 1.f + l) - glm::vec2(P) ) < 1.5f * scale;
glm::vec4 pos = glm::inverse(ctm) * glm::vec4( 0.12f, 0.12f, 0.f, 0.f );
picked = glm::length( glm::vec2( 1.f, 1.f) + glm::vec2(pos) - glm::vec2(P) ) < 1.5f * scale;
}
else if ( n.type() == Handles::SCALE ){
// the icon for scaling is on the right bottom corner at (0.12, -0.12) in scene coordinates
glm::vec4 vec = glm::inverse(modelview_) * glm::vec4( 0.1f, 0.1f, 0.f, 0.f );
float l = glm::length( glm::vec2(vec) );
picked = glm::length( glm::vec2( 1.f + l, -1.f - l) - glm::vec2(P) ) < 1.5f * scale;
glm::vec4 pos = glm::inverse(ctm) * glm::vec4( 0.12f, -0.12f, 0.f, 0.f );
picked = glm::length( glm::vec2( 1.f, -1.f) + glm::vec2(pos) - glm::vec2(P) ) < 1.5f * scale;
}
else if ( n.type() == Handles::CROP ){
// the icon for cropping is on the left bottom corner at (0.12, 0.12) in scene coordinates
glm::vec4 pos = glm::inverse(ctm) * glm::vec4( 0.12f, 0.12f, 0.f, 0.f );
picked = glm::length( glm::vec2( -1.f, -1.f) + glm::vec2(pos) - glm::vec2(P) ) < 1.5f * scale;
}
else if ( n.type() == Handles::MENU ){
// the icon for restore is on the left top corner at (-0.12, 0.12) in scene coordinates
glm::vec4 pos = glm::inverse(ctm) * glm::vec4( -0.12f, 0.12f, 0.f, 0.f );
picked = glm::length( glm::vec2( -1.f, 1.f) + glm::vec2(pos) - glm::vec2(P) ) < 1.5f * scale;
}
if ( picked )
@@ -165,6 +179,23 @@ void PickingVisitor::visit(Handles &n)
}
void PickingVisitor::visit(Symbol& n)
{
// discard if not visible or if not exactly one point given for picking
if ((!n.visible_ && !force_) || points_.size() != 1)
return;
// apply inverse transform to the point of interest
glm::vec4 P = glm::inverse(modelview_) * glm::vec4( points_[0], 1.f );
// test bounding box for picking from a single point
if ( n.bbox().contains( glm::vec3(P)) ) {
// add this to the nodes picked
nodes_.push_back( std::pair(&n, glm::vec2(P)) );
}
}
void PickingVisitor::visit(Scene &n)
{
n.root()->accept(*this);

View File

@@ -21,11 +21,12 @@ class PickingVisitor: public Visitor
std::vector<glm::vec3> points_;
glm::mat4 modelview_;
std::vector< std::pair<Node *, glm::vec2> > nodes_;
bool force_;
public:
PickingVisitor(glm::vec3 coordinates);
PickingVisitor(glm::vec3 selectionstart, glm::vec3 selection_end);
PickingVisitor(glm::vec3 coordinates, bool force = false);
PickingVisitor(glm::vec3 selectionstart, glm::vec3 selection_end, bool force = false);
bool empty() const {return nodes_.empty(); }
std::pair<Node *, glm::vec2> back() const { return nodes_.back(); }
@@ -49,6 +50,11 @@ public:
* @param n
*/
void visit(Handles& n) override;
/**
* @brief visit Disk : picking grabber for mixing view
* @param n
*/
void visit(Symbol& n) override;
/**
* @brief visit Disk : picking grabber for mixing view
* @param n

View File

@@ -85,8 +85,16 @@ void Surface::draw(glm::mat4 modelview, glm::mat4 projection)
if ( !initialized() )
init();
if ( textureindex_ )
glActiveTexture(GL_TEXTURE0);
if ( textureindex_ ) {
glBindTexture(GL_TEXTURE_2D, textureindex_);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // TODO add user input to select mode
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
}
else
glBindTexture(GL_TEXTURE_2D, Resource::getTextureBlack());
@@ -137,13 +145,12 @@ void MediaSurface::init()
void MediaSurface::draw(glm::mat4 modelview, glm::mat4 projection)
{
if ( !initialized() )
if ( !initialized() ) {
init();
// set the texture to the media player once openned
// TODO: avoid to repeat with a static flag?
if ( mediaplayer_->isOpen() )
textureindex_ = mediaplayer_->texture();
// set the texture to the media player once openned
if ( mediaplayer_->isOpen() )
textureindex_ = mediaplayer_->texture();
}
Surface::draw(modelview, projection);
}
@@ -164,19 +171,8 @@ void MediaSurface::accept(Visitor& v)
v.visit(*this);
}
FrameBufferSurface::FrameBufferSurface(FrameBuffer *fb, Shader *s) : Surface(s), frame_buffer_(fb)
{
}
void FrameBufferSurface::init()
{
Surface::init();
// set aspect ratio
scale_.x = frame_buffer_->aspectRatio();
}
void FrameBufferSurface::draw(glm::mat4 modelview, glm::mat4 projection)
@@ -197,7 +193,6 @@ void FrameBufferSurface::accept(Visitor& v)
v.visit(*this);
}
Points::Points(std::vector<glm::vec3> points, glm::vec4 color, uint pointsize) : Primitive(new Shader)
{
for(size_t i = 0; i < points.size(); ++i)
@@ -211,8 +206,6 @@ Points::Points(std::vector<glm::vec3> points, glm::vec4 color, uint pointsize) :
pointsize_ = pointsize;
}
void Points::draw(glm::mat4 modelview, glm::mat4 projection)
{
if ( !initialized() )

View File

@@ -94,7 +94,6 @@ class FrameBufferSurface : public Surface {
public:
FrameBufferSurface(FrameBuffer *fb, Shader *s = new ImageShader);
void init () override;
void draw (glm::mat4 modelview, glm::mat4 projection) override;
void accept (Visitor& v) override;

View File

@@ -58,8 +58,8 @@ and (recursively) clone all the internal git Dependencies.
**Ubuntu**
apt-get install build-essential cmake libpng-dev libglfw3-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev
apt-get install build-essential cmake libpng-dev libglfw3-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libicu-dev
**OSX with Brew**
brew install cmake libpng glfw gstreamer gst-libav gst-plugins-bad gst-plugins-base gst-plugins-good gst-plugins-ugly
brew install cmake libpng glfw gstreamer gst-libav gst-plugins-bad gst-plugins-base gst-plugins-good gst-plugins-ugly icu4c

View File

@@ -20,110 +20,107 @@
#include "Recorder.h"
// use glReadPixel or glGetTextImage
// read pixels & pbo should be the fastest
// https://stackoverflow.com/questions/38140527/glreadpixels-vs-glgetteximage
#define USE_GLREADPIXEL
using namespace std;
Recorder::Recorder() : finished_(false), pbo_index_(0), pbo_next_index_(0), size_(0)
PNGRecorder::PNGRecorder() : FrameGrabber()
{
pbo_[0] = pbo_[1] = 0;
}
PNGRecorder::PNGRecorder() : Recorder()
void PNGRecorder::init(GstCaps *caps)
{
// ignore
if (caps == nullptr)
return;
// create a gstreamer pipeline
std::string description = "appsrc name=src ! videoconvert ! pngenc ! filesink name=sink";
// parse pipeline descriptor
GError *error = NULL;
pipeline_ = gst_parse_launch (description.c_str(), &error);
if (error != NULL) {
Log::Warning("PNG Capture Could not construct pipeline %s:\n%s", description.c_str(), error->message);
g_clear_error (&error);
finished_ = true;
return;
}
// verify location path (path is always terminated by the OS dependent separator)
std::string path = SystemToolkit::path_directory(Settings::application.record.path);
if (path.empty())
path = SystemToolkit::home_path();
filename_ = path + "vimix_" + SystemToolkit::date_time_string() + ".png";
filename_ = path + SystemToolkit::date_time_string() + "_vimix.png";
// setup file sink
g_object_set (G_OBJECT (gst_bin_get_by_name (GST_BIN (pipeline_), "sink")),
"location", filename_.c_str(),
"sync", FALSE,
NULL);
}
// setup custom app source
src_ = GST_APP_SRC( gst_bin_get_by_name (GST_BIN (pipeline_), "src") );
if (src_) {
g_object_set (G_OBJECT (src_),
"stream-type", GST_APP_STREAM_TYPE_STREAM,
"is-live", TRUE,
"format", GST_FORMAT_TIME,
// "do-timestamp", TRUE,
NULL);
// Direct encoding (no buffering)
gst_app_src_set_max_bytes( src_, 0 );
// instruct src to use the required caps
caps_ = gst_caps_copy( caps );
gst_app_src_set_caps (src_, caps_);
// setup callbacks
GstAppSrcCallbacks callbacks;
callbacks.need_data = FrameGrabber::callback_need_data;
callbacks.enough_data = FrameGrabber::callback_enough_data;
callbacks.seek_data = NULL; // stream type is not seekable
gst_app_src_set_callbacks (src_, &callbacks, this, NULL);
// Thread to perform slow operation of saving to file
void save_png(std::string filename, unsigned char *data, uint w, uint h, uint c)
{
// got data to save ?
if (data) {
// save file
stbi_write_png(filename.c_str(), w, h, c, data, w * c);
// notify
Log::Notify("Capture %s ready (%d x %d %d)", filename.c_str(), w, h, c);
// done
free(data);
}
}
void PNGRecorder::addFrame(FrameBuffer *frame_buffer, float)
{
// ignore
if (frame_buffer == nullptr)
return;
// get what is needed from frame buffer
uint w = frame_buffer->width();
uint h = frame_buffer->height();
uint c = frame_buffer->use_alpha() ? 4 : 3;
// first iteration: initialize and get frame
if (size_ < 1)
{
// init size
size_ = w * h * c;
// create PBO
glGenBuffers(2, pbo_);
// set writing PBO
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo_[0]);
glBufferData(GL_PIXEL_PACK_BUFFER, size_, NULL, GL_STREAM_READ);
#ifdef USE_GLREADPIXEL
// get frame
frame_buffer->readPixels();
#else
glBindTexture(GL_TEXTURE_2D, frame_buffer->texture());
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
#endif
}
// second iteration; get frame and save file
else {
// set reading PBO
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo_[0]);
// get pixels
unsigned char* ptr = (unsigned char*) glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
if (NULL != ptr) {
// prepare memory buffer0
unsigned char * data = (unsigned char*) malloc(size_);
// transfer frame to data
memmove(data, ptr, size_);
// save in separate thread
std::thread(save_png, filename_, data, w, h, c).detach();
}
// unmap
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
// ok done
glDeleteBuffers(2, pbo_);
// recorded one frame
Log::Warning("PNG Capture Could not configure source");
finished_ = true;
return;
}
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
// start pipeline
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_PLAYING);
if (ret == GST_STATE_CHANGE_FAILURE) {
Log::Warning("PNG Capture Could not record %s", filename_.c_str());
finished_ = true;
return;
}
// unsigned char * data = (unsigned char*) malloc(size);
// GLenum format = frame_buffer->use_alpha() ? GL_RGBA : GL_RGB;
// glGetTextureSubImage( frame_buffer->texture(), 0, 0, 0, 0, w, h, 1, format, GL_UNSIGNED_BYTE, size, data);
// all good
Log::Info("PNG Capture started.");
// start recording !!
active_ = true;
}
void PNGRecorder::terminate()
{
Log::Notify("PNG Capture %s is ready.", filename_.c_str());
}
void PNGRecorder::addFrame(GstBuffer *buffer, GstCaps *caps, float dt)
{
FrameGrabber::addFrame(buffer, caps, dt);
// PNG Recorder specific :
// stop after one frame
if (timestamp_ > 0) {
stop();
}
}
const char* VideoRecorder::profile_name[VideoRecorder::DEFAULT] = {
"H264 (Baseline)",
"H264 (Realtime)",
"H264 (High 4:4:4)",
"H265 (Realtime)",
"H265 (HQ Animation)",
@@ -144,7 +141,12 @@ const std::vector<std::string> VideoRecorder::profile_description {
// veryfast (3)
// faster (4)
// fast (5)
"video/x-raw, format=I420 ! x264enc pass=4 quantizer=26 speed-preset=3 threads=4 ! video/x-h264, profile=baseline ! h264parse ! ",
#ifndef APPLE
// "video/x-raw, format=I420 ! x264enc pass=4 quantizer=26 speed-preset=3 threads=4 ! video/x-h264, profile=baseline ! h264parse ! ",
"video/x-raw, format=I420 ! x264enc tune=\"zerolatency\" threads=4 ! video/x-h264, profile=baseline ! h264parse ! ",
#else
"video/x-raw, format=I420 ! vtenc_h264_hw realtime=1 ! h264parse ! ",
#endif
"video/x-raw, format=Y444_10LE ! x264enc pass=4 quantizer=16 speed-preset=4 threads=4 ! video/x-h264, profile=(string)high-4:4:4 ! h264parse ! ",
// Control x265 encoder quality :
// NB: apparently x265 only accepts I420 format :(
@@ -163,7 +165,7 @@ const std::vector<std::string> VideoRecorder::profile_description {
// default 28
// 24 for x265 should be visually transparent; anything lower will probably just waste file size
"video/x-raw, format=I420 ! x265enc tune=4 speed-preset=3 ! video/x-h265, profile=(string)main ! h265parse ! ",
"video/x-raw, format=I420 ! x265enc tune=6 speed-preset=4 option-string=\"crf=22\" ! video/x-h265, profile=(string)main ! h265parse ! ",
"video/x-raw, format=I420 ! x265enc tune=6 speed-preset=4 option-string=\"crf=24\" ! video/x-h265, profile=(string)main ! h265parse ! ",
// Apple ProRes encoding parameters
// pass
// cbr (0) Constant Bitrate Encoding
@@ -195,298 +197,115 @@ const std::vector<std::string> VideoRecorder::profile_description {
// "qtmux ! filesink name=sink";
VideoRecorder::VideoRecorder() : Recorder(), frame_buffer_(nullptr), width_(0), height_(0),
recording_(false), accept_buffer_(false), pipeline_(nullptr), src_(nullptr), timestamp_(0)
VideoRecorder::VideoRecorder() : FrameGrabber()
{
// configure fix parameter
frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, 30); // 30 FPS
timeframe_ = 2 * frame_duration_;
}
VideoRecorder::~VideoRecorder()
void VideoRecorder::init(GstCaps *caps)
{
if (src_ != nullptr)
gst_object_unref (src_);
if (pipeline_ != nullptr) {
gst_element_set_state (pipeline_, GST_STATE_NULL);
gst_object_unref (pipeline_);
}
glDeleteBuffers(2, pbo_);
}
void VideoRecorder::addFrame (FrameBuffer *frame_buffer, float dt)
{
// TODO : avoid software videoconvert by using a GPU shader to produce Y444 frames
// ignore
if (frame_buffer == nullptr)
if (caps == nullptr)
return;
// first frame for initialization
if (frame_buffer_ == nullptr) {
// create a gstreamer pipeline
std::string description = "appsrc name=src ! videoconvert ! ";
if (Settings::application.record.profile < 0 || Settings::application.record.profile >= DEFAULT)
Settings::application.record.profile = H264_STANDARD;
description += profile_description[Settings::application.record.profile];
// set frame buffer as input
frame_buffer_ = frame_buffer;
// verify location path (path is always terminated by the OS dependent separator)
std::string path = SystemToolkit::path_directory(Settings::application.record.path);
if (path.empty())
path = SystemToolkit::home_path();
// define stream properties
width_ = frame_buffer_->width();
height_ = frame_buffer_->height();
size_ = width_ * height_ * (frame_buffer_->use_alpha() ? 4 : 3);
// setup filename & muxer
if( Settings::application.record.profile == JPEG_MULTI) {
std::string folder = path + "vimix_" + SystemToolkit::date_time_string();
filename_ = SystemToolkit::full_filename(folder, "%05d.jpg");
if (SystemToolkit::create_directory(folder))
description += "multifilesink name=sink";
}
else if( Settings::application.record.profile == VP8) {
filename_ = path + "vimix_" + SystemToolkit::date_time_string() + ".webm";
description += "webmmux ! filesink name=sink";
}
else {
filename_ = path + "vimix_" + SystemToolkit::date_time_string() + ".mov";
description += "qtmux ! filesink name=sink";
}
// create PBOs
glGenBuffers(2, pbo_);
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo_[1]);
glBufferData(GL_PIXEL_PACK_BUFFER, size_, NULL, GL_STREAM_READ);
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo_[0]);
glBufferData(GL_PIXEL_PACK_BUFFER, size_, NULL, GL_STREAM_READ);
// parse pipeline descriptor
GError *error = NULL;
pipeline_ = gst_parse_launch (description.c_str(), &error);
if (error != NULL) {
Log::Warning("VideoRecorder Could not construct pipeline %s:\n%s", description.c_str(), error->message);
g_clear_error (&error);
finished_ = true;
return;
}
// create a gstreamer pipeline
string description = "appsrc name=src ! videoconvert ! ";
if (Settings::application.record.profile < 0 || Settings::application.record.profile >= DEFAULT)
Settings::application.record.profile = H264_STANDARD;
description += profile_description[Settings::application.record.profile];
// setup file sink
g_object_set (G_OBJECT (gst_bin_get_by_name (GST_BIN (pipeline_), "sink")),
"location", filename_.c_str(),
"sync", FALSE,
NULL);
// verify location path (path is always terminated by the OS dependent separator)
std::string path = SystemToolkit::path_directory(Settings::application.record.path);
if (path.empty())
path = SystemToolkit::home_path();
// setup custom app source
src_ = GST_APP_SRC( gst_bin_get_by_name (GST_BIN (pipeline_), "src") );
if (src_) {
// setup filename & muxer
if( Settings::application.record.profile == JPEG_MULTI) {
std::string folder = path + SystemToolkit::date_time_string() + "_vimix_jpg";
filename_ = SystemToolkit::full_filename(folder, "%05d.jpg");
if (SystemToolkit::create_directory(folder))
description += "multifilesink name=sink";
}
else if( Settings::application.record.profile == VP8) {
filename_ = path + SystemToolkit::date_time_string() + "_vimix.webm";
description += "webmmux ! filesink name=sink";
}
else {
filename_ = path + SystemToolkit::date_time_string() + "_vimix.mov";
description += "qtmux ! filesink name=sink";
}
g_object_set (G_OBJECT (src_),
"stream-type", GST_APP_STREAM_TYPE_STREAM,
"is-live", TRUE,
"format", GST_FORMAT_TIME,
// "do-timestamp", TRUE,
NULL);
// parse pipeline descriptor
GError *error = NULL;
pipeline_ = gst_parse_launch (description.c_str(), &error);
if (error != NULL) {
Log::Warning("VideoRecorder Could not construct pipeline %s:\n%s", description.c_str(), error->message);
g_clear_error (&error);
finished_ = true;
return;
}
// Direct encoding (no buffering)
gst_app_src_set_max_bytes( src_, 0 );
// setup file sink
g_object_set (G_OBJECT (gst_bin_get_by_name (GST_BIN (pipeline_), "sink")),
"location", filename_.c_str(),
"sync", FALSE,
NULL);
// instruct src to use the required caps
caps_ = gst_caps_copy( caps );
gst_app_src_set_caps (src_, caps_);
// setup custom app source
src_ = GST_APP_SRC( gst_bin_get_by_name (GST_BIN (pipeline_), "src") );
if (src_) {
// setup callbacks
GstAppSrcCallbacks callbacks;
callbacks.need_data = FrameGrabber::callback_need_data;
callbacks.enough_data = FrameGrabber::callback_enough_data;
callbacks.seek_data = NULL; // stream type is not seekable
gst_app_src_set_callbacks (src_, &callbacks, this, NULL);
g_object_set (G_OBJECT (src_),
"stream-type", GST_APP_STREAM_TYPE_STREAM,
"is-live", TRUE,
"format", GST_FORMAT_TIME,
// "do-timestamp", TRUE,
NULL);
}
else {
Log::Warning("VideoRecorder Could not configure source");
finished_ = true;
return;
}
// Direct encoding (no buffering)
gst_app_src_set_max_bytes( src_, 0 );
// gst_app_src_set_max_bytes( src_, 2 * buf_size_);
// start recording
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_PLAYING);
if (ret == GST_STATE_CHANGE_FAILURE) {
Log::Warning("VideoRecorder Could not record %s", filename_.c_str());
finished_ = true;
return;
}
// instruct src to use the required caps
GstCaps *caps = gst_caps_new_simple ("video/x-raw",
"format", G_TYPE_STRING, frame_buffer_->use_alpha() ? "RGBA" : "RGB",
"width", G_TYPE_INT, width_,
"height", G_TYPE_INT, height_,
"framerate", GST_TYPE_FRACTION, 30, 1,
NULL);
gst_app_src_set_caps (src_, caps);
gst_caps_unref (caps);
// setup callbacks
GstAppSrcCallbacks callbacks;
callbacks.need_data = callback_need_data;
callbacks.enough_data = callback_enough_data;
callbacks.seek_data = NULL; // stream type is not seekable
gst_app_src_set_callbacks (src_, &callbacks, this, NULL);
}
else {
Log::Warning("VideoRecorder Could not configure capture source");
finished_ = true;
return;
}
// start recording
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_PLAYING);
if (ret == GST_STATE_CHANGE_FAILURE) {
Log::Warning("VideoRecorder Could not record %s", filename_.c_str());
finished_ = true;
return;
}
// all good
Log::Info("VideoRecorder start recording (%s %d x %d)", profile_name[Settings::application.record.profile], width_, height_);
// start recording !!
recording_ = true;
}
// frame buffer changed ?
else if (frame_buffer_ != frame_buffer) {
// if an incompatilble frame buffer given: stop recorder
if ( frame_buffer->width() != width_ ||
frame_buffer->height() != height_ ||
frame_buffer->use_alpha() != frame_buffer_->use_alpha()) {
stop();
Log::Warning("Recording interrupted: new session (%d x %d) incompatible with recording (%d x %d)", frame_buffer->width(), frame_buffer->height(), width_, height_);
}
else {
// accepting a new frame buffer as input
frame_buffer_ = frame_buffer;
}
}
// store a frame if recording is active
if (recording_ && size_ > 0)
{
// calculate dt in ns
timeframe_ += gst_gdouble_to_guint64( dt * 1000000.f);
// if time is passed one frame duration (with 10% margin)
// and if the encoder accepts data
if ( timeframe_ > frame_duration_ - 3000000 && accept_buffer_) {
// set buffer target for writing in a new frame
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo_[pbo_index_]);
#ifdef USE_GLREADPIXEL
// get frame
frame_buffer->readPixels();
#else
glBindTexture(GL_TEXTURE_2D, frame_buffer->texture());
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
#endif
// update case ; alternating indices
if ( pbo_next_index_ != pbo_index_ ) {
// set buffer target for saving the frame
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo_[pbo_next_index_]);
// new buffer
GstBuffer *buffer = gst_buffer_new_and_alloc (size_);
// set timing of buffer
buffer->pts = timestamp_;
buffer->duration = frame_duration_;
// map gst buffer into a memory WRITE target
GstMapInfo map;
gst_buffer_map (buffer, &map, GST_MAP_WRITE);
// map PBO pixels into a memory READ pointer
unsigned char* ptr = (unsigned char*) glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
// transfer pixels from PBO memory to buffer memory
if (NULL != ptr)
memmove(map.data, ptr, size_);
// un-map
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
gst_buffer_unmap (buffer, &map);
// push
// Log::Info("VideoRecorder push data %ld", buffer->pts);
gst_app_src_push_buffer (src_, buffer);
// NB: buffer will be unrefed by the appsrc
// next timestamp
timestamp_ += frame_duration_;
}
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
// alternate indices
pbo_next_index_ = pbo_index_;
pbo_index_ = (pbo_index_ + 1) % 2;
// restart frame counter
timeframe_ = 0;
}
}
// did the recording terminate with sink receiving end-of-stream ?
else
{
// Wait for EOS message
GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline_));
GstMessage *msg = gst_bus_poll(bus, GST_MESSAGE_EOS, GST_TIME_AS_USECONDS(1));
if (msg) {
// Log::Info("received EOS");
// stop the pipeline
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_NULL);
if (ret == GST_STATE_CHANGE_FAILURE)
Log::Warning("VideoRecorder Could not stop");
else
Log::Notify("Recording %s ready.", filename_.c_str());
finished_ = true;
}
}
// all good
Log::Info("Video Recording started (%s)", profile_name[Settings::application.record.profile]);
// start recording !!
active_ = true;
}
void VideoRecorder::stop ()
void VideoRecorder::terminate()
{
// send end of stream
gst_app_src_end_of_stream (src_);
// Log::Info("VideoRecorder push EOS");
// stop recording
recording_ = false;
Log::Notify("Video Recording %s is ready.", filename_.c_str());
}
std::string VideoRecorder::info()
std::string VideoRecorder::info() const
{
if (recording_)
if (active_)
return GstToolkit::time_to_string(timestamp_);
else
return "Saving file...";
}
double VideoRecorder::duration()
{
return gst_guint64_to_gdouble( GST_TIME_AS_MSECONDS(timestamp_) ) / 1000.0;
}
// appsrc needs data and we should start sending
void VideoRecorder::callback_need_data (GstAppSrc *, guint , gpointer p)
{
// Log::Info("H264Recording callback_need_data");
VideoRecorder *rec = (VideoRecorder *)p;
if (rec) {
rec->accept_buffer_ = rec->recording_ ? true : false;
}
}
// appsrc has enough data and we can stop sending
void VideoRecorder::callback_enough_data (GstAppSrc *, gpointer p)
{
// Log::Info("H264Recording callback_enough_data");
VideoRecorder *rec = (VideoRecorder *)p;
if (rec) {
rec->accept_buffer_ = false;
}
}

View File

@@ -1,78 +1,36 @@
#ifndef RECORDER_H
#define RECORDER_H
#include <atomic>
#include <string>
#include <vector>
#include <gst/pbutils/pbutils.h>
#include <gst/app/gstappsrc.h>
class FrameBuffer;
#include "FrameGrabber.h"
/**
* @brief The Recorder class defines the base class for all recorders
* used to save images or videos from a frame buffer.
*
* The Mixer class calls addFrame() at each newly rendered frame for all of its recorder.
*/
class Recorder
{
public:
Recorder();
virtual ~Recorder() {}
virtual void addFrame(FrameBuffer *frame_buffer, float dt) = 0;
virtual void stop() { }
virtual std::string info() { return ""; }
virtual double duration() { return 0.0; }
inline bool finished() const { return finished_; }
protected:
// thread-safe testing termination
std::atomic<bool> finished_;
// PBO
guint pbo_[2];
guint pbo_index_, pbo_next_index_;
guint size_;
};
class PNGRecorder : public Recorder
class PNGRecorder : public FrameGrabber
{
std::string filename_;
public:
PNGRecorder();
void addFrame(FrameBuffer *frame_buffer, float) override;
protected:
void init(GstCaps *caps) override;
void terminate() override;
void addFrame(GstBuffer *buffer, GstCaps *caps, float dt) override;
};
class VideoRecorder : public Recorder
class VideoRecorder : public FrameGrabber
{
std::string filename_;
// Frame buffer information
FrameBuffer *frame_buffer_;
uint width_;
uint height_;
// operation
std::atomic<bool> recording_;
std::atomic<bool> accept_buffer_;
// gstreamer pipeline
GstElement *pipeline_;
GstAppSrc *src_;
GstClockTime timeframe_;
GstClockTime timestamp_;
GstClockTime frame_duration_;
static void callback_need_data (GstAppSrc *, guint, gpointer user_data);
static void callback_enough_data (GstAppSrc *, gpointer user_data);
void init(GstCaps *caps) override;
void terminate() override;
public:
@@ -91,13 +49,7 @@ public:
static const std::vector<std::string> profile_description;
VideoRecorder();
~VideoRecorder();
void addFrame(FrameBuffer *frame_buffer, float dt) override;
void stop() override;
std::string info() override;
double duration() override;
std::string info() const override;
};

View File

@@ -68,7 +68,7 @@ static void WindowRefreshCallback( GLFWwindow * )
static void WindowResizeCallback( GLFWwindow *w, int width, int height)
{
int id = GLFW_window_[w]->id();
int id = GLFW_window_[w]->index();
if (!Settings::application.windows[id].fullscreen) {
Settings::application.windows[id].w = width;
Settings::application.windows[id].h = height;
@@ -77,19 +77,19 @@ static void WindowResizeCallback( GLFWwindow *w, int width, int height)
static void WindowMoveCallback( GLFWwindow *w, int x, int y)
{
int id = GLFW_window_[w]->id();
int id = GLFW_window_[w]->index();
if (!Settings::application.windows[id].fullscreen) {
Settings::application.windows[id].x = x;
Settings::application.windows[id].y = y;
}
}
static void WindowEscapeFullscreen( GLFWwindow *w, int key, int scancode, int action, int)
static void WindowEscapeFullscreen( GLFWwindow *w, int key, int, int action, int)
{
if (action == GLFW_PRESS && key == GLFW_KEY_ESCAPE)
{
// escape fullscreen
GLFW_window_[w]->setFullscreen(nullptr);
GLFW_window_[w]->exitFullscreen();
}
}
@@ -157,6 +157,15 @@ bool Rendering::init()
g_setenv ("GST_GL_API", "opengl3", TRUE);
gst_init (NULL, NULL);
// increase selection rank for GPU decoding plugins
if (Settings::application.render.gpu_decoding) {
std::list<std::string> gpuplugins = GstToolkit::enable_gpu_decoding_plugins();
if (gpuplugins.size() > 0) {
Log::Info("Video decoding favoring the following GPU decoding plugin(s):");
for(auto it = gpuplugins.begin(); it != gpuplugins.end(); it++)
Log::Info(" - %s", (*it).c_str());
}
}
//#if GST_GL_HAVE_PLATFORM_WGL
// global_gl_context = gst_gl_context_new_wrapped (display, (guintptr) wglGetCurrentContext (),
@@ -186,9 +195,6 @@ bool Rendering::init()
glfwSetKeyCallback( output_.window(), WindowEscapeFullscreen);
glfwSetMouseButtonCallback( output_.window(), WindowToggleFullscreen);
// GstDeviceMonitor *dm = GstToolkit::setup_raw_video_source_device_monitor();
return true;
}
@@ -223,7 +229,6 @@ void Rendering::pushBackDrawCallback(RenderingCallback function)
void Rendering::draw()
{
// operate on main window context
main_.makeCurrent();
@@ -261,8 +266,19 @@ void Rendering::draw()
// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
glfwPollEvents();
// change windows
main_.toggleFullscreen_();
output_.toggleFullscreen_();
// no g_main_loop_run(loop) : update global GMainContext
g_main_context_iteration(NULL, FALSE);
// software framerate limiter 60FPS if not v-sync
if ( Settings::application.render.vsync < 1 ) {
int dt = 18000 - int( 1000.f * Mixer::manager().dt() );
if (dt > 100)
g_usleep( dt );
}
}
@@ -398,7 +414,7 @@ WindowSurface::WindowSurface(Shader *s) : Primitive(s)
RenderingWindow::RenderingWindow() : window_(nullptr), master_(nullptr),
id_(-1), dpi_scale_(1.f), textureid_(0), fbo_(0), surface_(nullptr)
index_(-1), dpi_scale_(1.f), textureid_(0), fbo_(0), surface_(nullptr), request_toggle_fullscreen_(false)
{
}
@@ -412,7 +428,7 @@ RenderingWindow::~RenderingWindow()
void RenderingWindow::setTitle(const std::string &title)
{
std::string fulltitle = Settings::application.windows[id_].name;
std::string fulltitle = Settings::application.windows[index_].name;
if ( !title.empty() )
fulltitle += " -- " + title;
@@ -433,8 +449,8 @@ void RenderingWindow::setIcon(const std::string &resource)
bool RenderingWindow::isFullscreen ()
{
return (glfwGetWindowMonitor(window_) != nullptr);
// return Settings::application.windows[id_].fullscreen;
// return (glfwGetWindowMonitor(window_) != nullptr);
return Settings::application.windows[index_].fullscreen;
}
GLFWmonitor *RenderingWindow::monitorAt(int x, int y)
@@ -502,45 +518,72 @@ GLFWmonitor *RenderingWindow::monitor()
// pick at the coordinates given or at pos of window
int x, y;
glfwGetWindowPos(window_, &x, &y);
return monitorAt(x, y);
}
void RenderingWindow::setFullscreen(GLFWmonitor *mo)
void RenderingWindow::setFullscreen_(GLFWmonitor *mo)
{
// done request
request_toggle_fullscreen_ = false;
// if in fullscreen mode
if (mo == nullptr) {
// store fullscreen mode
Settings::application.windows[index_].fullscreen = false;
// set to window mode
glfwSetInputMode( window_, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
glfwSetWindowMonitor( window_, nullptr, Settings::application.windows[id_].x,
Settings::application.windows[id_].y,
Settings::application.windows[id_].w,
Settings::application.windows[id_].h, 0 );
Settings::application.windows[id_].fullscreen = false;
glfwSetWindowMonitor( window_, nullptr, Settings::application.windows[index_].x,
Settings::application.windows[index_].y,
Settings::application.windows[index_].w,
Settings::application.windows[index_].h, 0 );
}
// not in fullscreen mode
else {
// set to fullscreen mode
Settings::application.windows[id_].fullscreen = true;
Settings::application.windows[id_].monitor = glfwGetMonitorName(mo);
// store fullscreen mode
Settings::application.windows[index_].fullscreen = true;
Settings::application.windows[index_].monitor = glfwGetMonitorName(mo);
// set to fullscreen mode
const GLFWvidmode * mode = glfwGetVideoMode(mo);
glfwSetWindowMonitor( window_, mo, 0, 0, mode->width, mode->height, mode->refreshRate);
glfwSetInputMode( window_, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
glfwSetWindowMonitor( window_, mo, 0, 0, mode->width, mode->height, mode->refreshRate);
// Enable vsync on output window only (i.e. not 0 if has a master)
// Workaround for disabled vsync in fullscreen (https://github.com/glfw/glfw/issues/1072)
glfwSwapInterval( nullptr == master_ ? 0 : Settings::application.render.vsync);
}
}
void RenderingWindow::exitFullscreen()
{
if (isFullscreen()) {
// exit fullscreen
request_toggle_fullscreen_ = true;
}
}
void RenderingWindow::toggleFullscreen()
{
// if in fullscreen mode
if (isFullscreen()) {
// exit fullscreen
setFullscreen(nullptr);
}
// not in fullscreen mode
else {
// enter fullscreen in monitor where the window is
setFullscreen(monitor());
request_toggle_fullscreen_ = true;
}
void RenderingWindow::toggleFullscreen_()
{
if (request_toggle_fullscreen_) {
// if in fullscreen mode
if (glfwGetWindowMonitor(window_) != nullptr) {
// exit fullscreen
setFullscreen_(nullptr);
}
// not in fullscreen mode
else {
// enter fullscreen in monitor where the window is
setFullscreen_(monitor());
}
}
}
@@ -554,9 +597,21 @@ int RenderingWindow::height()
return window_attributes_.viewport.y;
}
int RenderingWindow::maxHeight()
int RenderingWindow::pixelsforRealHeight(float milimeters)
{
return static_cast<int>( static_cast<float>(glfwGetVideoMode(monitor())->height) * dpi_scale_);
GLFWmonitor *mo = monitor();
int mm_w = 0;
int mm_h = 0;
glfwGetMonitorPhysicalSize(mo, &mm_w, &mm_h);
float pixels = milimeters;
if (mm_h > 0)
pixels *= static_cast<float>(glfwGetVideoMode(mo)->height) / static_cast<float>(mm_h);
else
pixels *= 5; // something reasonnable if monitor's physical size is unknown
return static_cast<int>( round(pixels) );
}
float RenderingWindow::aspectRatio()
@@ -564,13 +619,13 @@ float RenderingWindow::aspectRatio()
return static_cast<float>(window_attributes_.viewport.x) / static_cast<float>(window_attributes_.viewport.y);
}
bool RenderingWindow::init(int id, GLFWwindow *share)
bool RenderingWindow::init(int index, GLFWwindow *share)
{
id_ = id;
index_ = index;
master_ = share;
// access Settings
Settings::WindowConfig winset = Settings::application.windows[id_];
Settings::WindowConfig winset = Settings::application.windows[index_];
// do not show at creation
glfwWindowHint(GLFW_FOCUSED, GLFW_FALSE);
@@ -580,7 +635,7 @@ bool RenderingWindow::init(int id, GLFWwindow *share)
// create the window normal
window_ = glfwCreateWindow(winset.w, winset.h, winset.name.c_str(), NULL, master_);
if (window_ == NULL){
Log::Error("Failed to create GLFW Window %d", id_);
Log::Error("Failed to create GLFW Window %d", index_);
return false;
}
@@ -634,7 +689,7 @@ bool RenderingWindow::init(int id, GLFWwindow *share)
glfwMakeContextCurrent(master_);
}
else {
// Disable vsync on main window
// Disable vsync on main window
glfwSwapInterval(0);
// Enable Antialiasing multisampling
if (Settings::application.render.multisampling > 0) {
@@ -652,9 +707,9 @@ void RenderingWindow::show()
{
glfwShowWindow(window_);
if ( Settings::application.windows[id_].fullscreen ) {
GLFWmonitor *mo = monitorNamed(Settings::application.windows[id_].monitor);
setFullscreen(mo);
if ( Settings::application.windows[index_].fullscreen ) {
GLFWmonitor *mo = monitorNamed(Settings::application.windows[index_].monitor);
setFullscreen_(mo);
}
}

View File

@@ -24,9 +24,11 @@ struct RenderingAttrib
class RenderingWindow
{
friend class Rendering;
GLFWwindow *window_, *master_;
RenderingAttrib window_attributes_;
int id_;
int index_;
float dpi_scale_;
// objects to render
@@ -34,16 +36,19 @@ class RenderingWindow
uint fbo_;
class WindowSurface *surface_;
bool request_toggle_fullscreen_;
void toggleFullscreen_ ();
void setFullscreen_(GLFWmonitor *mo);
public:
RenderingWindow();
~RenderingWindow();
inline int id() const { return id_; }
inline int index() const { return index_; }
inline RenderingAttrib& attribs() { return window_attributes_; }
inline GLFWwindow *window() const { return window_; }
bool init(int id, GLFWwindow *share = NULL);
bool init(int index, GLFWwindow *share = NULL);
void setIcon(const std::string &resource);
void setTitle(const std::string &title = "");
@@ -58,7 +63,7 @@ public:
// fullscreen
bool isFullscreen ();
void setFullscreen(GLFWmonitor *mo);
void exitFullscreen();
void toggleFullscreen ();
// get width of rendering area
@@ -67,8 +72,8 @@ public:
int height();
// get aspect ratio of rendering area
float aspectRatio();
// get total height available in monitor
int maxHeight();
// get number of pixels to render X milimeters in height
int pixelsforRealHeight(float milimeters);
inline float dpiScale() const { return dpi_scale_; }
@@ -122,7 +127,7 @@ public:
void popAttrib();
RenderingAttrib currentAttrib();
// get hold on the main window
// get hold on the windows
inline RenderingWindow& mainWindow() { return main_; }
inline RenderingWindow& outputWindow() { return output_; }

View File

@@ -33,6 +33,10 @@ uint Resource::getTextureBlack()
if (tex_index_black == 0) {
glGenTextures(1, &tex_index_black);
glBindTexture( GL_TEXTURE_2D, tex_index_black);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
unsigned char clearColor[4] = {0, 0, 0, 255};
// texture with one black pixel
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1);
@@ -50,6 +54,10 @@ uint Resource::getTextureWhite()
if (tex_index_white == 0) {
glGenTextures(1, &tex_index_white);
glBindTexture( GL_TEXTURE_2D, tex_index_white);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
unsigned char clearColor[4] = {255, 255, 255, 255};
// texture with one black pixel
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1);
@@ -59,6 +67,27 @@ uint Resource::getTextureWhite()
return tex_index_white;
}
uint Resource::getTextureTransparent()
{
static uint tex_index_transparent = 0;
// generate texture (once)
if (tex_index_transparent == 0) {
glGenTextures(1, &tex_index_transparent);
glBindTexture( GL_TEXTURE_2D, tex_index_transparent);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
unsigned char clearColor[4] = {0, 0, 0, 0};
// texture with one black pixel
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, clearColor);
}
return tex_index_transparent;
}
const char *Resource::getData(const std::string& path, size_t* out_file_size){
auto fs = cmrc::vmix::get_filesystem();
@@ -248,6 +277,8 @@ uint Resource::getTextureImage(const std::string& path, float *aspect_ratio)
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, img);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// free memory
stbi_image_free(img);

View File

@@ -22,12 +22,15 @@ namespace Resource
// Returns the OpenGL generated Texture index
uint getTextureImage(const std::string& path, float *aspect_ratio = nullptr);
// Returns the OpenGL generated Texture index for an empty 1x1 black transparent pixel texture
// Returns the OpenGL generated Texture index for an empty 1x1 black opaque pixel texture
uint getTextureBlack();
// Returns the OpenGL generated Texture index for an empty 1x1 white opaque pixel texture
uint getTextureWhite();
// Returns the OpenGL generated Texture index for an empty 1x1 back transparent pixel texture
uint getTextureTransparent();
// Generic access to pointer to data
const char *getData(const std::string& path, size_t* out_file_size);

View File

@@ -15,21 +15,19 @@
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/random.hpp>
#include <chrono>
#include <ctime>
#include <algorithm>
// Node
Node::Node() : initialized_(false), visible_(true), refcount_(0)
{
// create unique id
auto duration = std::chrono::high_resolution_clock::now().time_since_epoch();
id_ = std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count() % 100000000;
id_ = GlmToolkit::uniqueId();
transform_ = glm::identity<glm::mat4>();
scale_ = glm::vec3(1.f);
rotation_ = glm::vec3(0.f);
translation_ = glm::vec3(0.f);
crop_ = glm::vec3(1.f);
}
Node::~Node ()
@@ -56,6 +54,7 @@ void Node::copyTransform(Node *other)
scale_ = other->scale_;
rotation_ = other->rotation_;
translation_ = other->translation_;
crop_ = other->crop_;
}
void Node::update( float dt)
@@ -199,9 +198,16 @@ void Primitive::accept(Visitor& v)
void Primitive::replaceShader( Shader *newshader )
{
if (newshader) {
if (shader_)
glm::mat4 iTransform = newshader->iTransform;
glm::vec4 color = newshader->color;
if (shader_) {
iTransform = shader_->iTransform;
color = shader_->color;
delete shader_;
}
shader_ = newshader;
shader_->iTransform = iTransform;
shader_->color = color;
}
}
@@ -231,8 +237,10 @@ void Group::clear()
void Group::attach(Node *child)
{
children_.insert(child);
child->refcount_++;
if (child != nullptr) {
children_.insert(child);
child->refcount_++;
}
}
@@ -245,17 +253,18 @@ void Group::sort()
children_.swap(ordered_children);
}
void Group::detatch(Node *child)
void Group::detach(Node *child)
{
// find the node with this id, and erase it out of the list of children
// NB: do NOT delete with remove : this takes all nodes with same depth (i.e. equal depth in set)
NodeSet::iterator it = std::find_if(children_.begin(), children_.end(), hasId(child->id()));
if ( it != children_.end()) {
// detatch child from group parent
children_.erase(it);
child->refcount_--;
if (child != nullptr) {
// find the node with this id, and erase it out of the list of children
// NB: do NOT delete with remove : this takes all nodes with same depth (i.e. equal depth in set)
NodeSet::iterator it = std::find_if(children_.begin(), children_.end(), hasId(child->id()));
if ( it != children_.end()) {
// detatch child from group parent
children_.erase(it);
child->refcount_--;
}
}
}
void Group::update( float dt )

12
Scene.h
View File

@@ -41,7 +41,7 @@ class Group;
*/
class Node {
int id_;
uint64_t id_;
bool initialized_;
public:
@@ -49,7 +49,7 @@ public:
virtual ~Node ();
// unique identifyer generated at instanciation
inline int id () const { return id_; }
inline uint64_t id () const { return id_; }
// must initialize the node before draw
virtual void init () { initialized_ = true; }
@@ -70,7 +70,7 @@ public:
bool visible_;
uint refcount_;
glm::mat4 transform_;
glm::vec3 scale_, rotation_, translation_;
glm::vec3 scale_, rotation_, translation_, crop_;
// animation update callbacks
// list of callbacks to call at each update
@@ -140,9 +140,9 @@ struct hasId: public std::unary_function<Node*, bool>
{
return (e && e->id() == _id);
}
hasId(int id) : _id(id) { }
hasId(uint64_t id) : _id(id) { }
private:
int _id;
uint64_t _id;
};
@@ -173,7 +173,7 @@ public:
// container
void clear();
void attach (Node *child);
void detatch (Node *child);
void detach (Node *child);
inline uint numChildren () const { return children_.size(); }
// Group specific access to its Nodes

View File

@@ -24,8 +24,10 @@ Screenshot::Screenshot()
Screenshot::~Screenshot()
{
glDeleteBuffers(1, &Pbo);
if (Data) free(Data);
if (Pbo > 0)
glDeleteBuffers(1, &Pbo);
if (Data)
free(Data);
}
bool Screenshot::isFull()
@@ -37,7 +39,7 @@ void Screenshot::captureGL(int x, int y, int w, int h)
{
Width = w - x;
Height = h - y;
unsigned int size = Width * Height * 4;
unsigned int size = Width * Height * 3;
// create BPO
if (Pbo == 0)
@@ -56,7 +58,7 @@ void Screenshot::captureGL(int x, int y, int w, int h)
// screenshot to PBO (fast)
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glReadPixels(x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, 0);
glReadPixels(x, y, w, h, GL_RGB, GL_UNSIGNED_BYTE, 0);
Pbo_full = true;
// done
@@ -83,6 +85,7 @@ void Screenshot::save(std::string filename)
// ready for next
Pbo_full = false;
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
}
}
@@ -126,11 +129,9 @@ void Screenshot::storeToFile(Screenshot *s, std::string filename)
ScreenshotSavePending_ = true;
// got data to save ?
if (s && s->Data) {
// make it usable
s->RemoveAlpha();
s->FlipVertical();
// save file
stbi_write_png(filename.c_str(), s->Width, s->Height, 4, s->Data, s->Width * 4);
stbi_flip_vertically_on_write(true);
stbi_write_png(filename.c_str(), s->Width, s->Height, 3, s->Data, s->Width * 3);
}
ScreenshotSavePending_ = false;
}

View File

@@ -1,5 +1,9 @@
#include <algorithm>
#include "defines.h"
#include "SessionVisitor.h"
#include <tinyxml2.h>
#include "Selection.h"
Selection::Selection()
@@ -107,6 +111,9 @@ uint Selection::size()
Source *Selection::front()
{
if (selection_.empty())
return nullptr;
return selection_.front();
}
@@ -136,4 +143,29 @@ SourceList::iterator Selection::end()
return selection_.end();
}
std::string Selection::xml()
{
std::string x = "";
if (!selection_.empty()) {
// create xml doc and root node
tinyxml2::XMLDocument xmlDoc;
tinyxml2::XMLElement *selectionNode = xmlDoc.NewElement(APP_NAME);
selectionNode->SetAttribute("size", (int) selection_.size());
xmlDoc.InsertEndChild(selectionNode);
// fill doc
SessionVisitor sv(&xmlDoc, selectionNode);
for (auto iter = selection_.begin(); iter != selection_.end(); iter++, sv.setRoot(selectionNode) )
(*iter)->accept(sv);
// get compact string
tinyxml2::XMLPrinter xmlPrint(0, true);
xmlDoc.Print( &xmlPrint );
x = xmlPrint.CStr();
}
return x;
}

View File

@@ -26,6 +26,8 @@ public:
bool empty();
uint size ();
std::string xml();
protected:
SourceList::iterator find (Source *s);
SourceList selection_;

View File

@@ -5,7 +5,7 @@
#include "FrameBuffer.h"
#include "Session.h"
#include "GarbageVisitor.h"
#include "Recorder.h"
#include "FrameGrabber.h"
#include "SessionCreator.h"
#include "Log.h"
@@ -28,14 +28,15 @@ Session::Session() : failedSource_(nullptr), active_(true), fading_target_(0.f)
config_[View::MIXING] = new Group;
config_[View::MIXING]->scale_ = Settings::application.views[View::MIXING].default_scale;
config_[View::MIXING]->translation_ = Settings::application.views[View::MIXING].default_translation;
config_[View::APPEARANCE] = new Group;
config_[View::APPEARANCE]->scale_ = Settings::application.views[View::APPEARANCE].default_scale;
config_[View::APPEARANCE]->translation_ = Settings::application.views[View::APPEARANCE].default_translation;
}
Session::~Session()
{
// delete all recorders
clearRecorders();
// delete all sources
for(auto it = sources_.begin(); it != sources_.end(); ) {
// erase this source from the list
@@ -46,6 +47,7 @@ Session::~Session()
delete config_[View::GEOMETRY];
delete config_[View::LAYER];
delete config_[View::MIXING];
delete config_[View::APPEARANCE];
}
void Session::setActive (bool on)
@@ -89,22 +91,9 @@ void Session::update(float dt)
// draw render view in Frame Buffer
render_.draw();
// send frame to recorders
std::list<Recorder *>::iterator iter;
for (iter=recorders_.begin(); iter != recorders_.end(); )
{
Recorder *rec = *iter;
// grab frames to recorders & streamers
FrameGrabbing::manager().grabFrame(render_.frame(), dt);
rec->addFrame(render_.frame(), dt);
if (rec->finished()) {
iter = recorders_.erase(iter);
delete rec;
}
else {
iter++;
}
}
}
@@ -113,10 +102,15 @@ SourceList::iterator Session::addSource(Source *s)
// lock before change
access_.lock();
// insert the source in the rendering
render_.scene.ws()->attach(s->group(View::RENDERING));
// insert the source to the beginning of the list
sources_.push_front(s);
// find the source
SourceList::iterator its = find(s);
// ok, its NOT in the list !
if (its == sources_.end()) {
// insert the source in the rendering
render_.scene.ws()->attach(s->group(View::RENDERING));
// insert the source to the beginning of the list
sources_.push_front(s);
}
// unlock access
access_.unlock();
@@ -134,13 +128,10 @@ SourceList::iterator Session::deleteSource(Source *s)
SourceList::iterator its = find(s);
// ok, its in the list !
if (its != sources_.end()) {
// remove Node from the rendering scene
render_.scene.ws()->detatch( s->group(View::RENDERING) );
render_.scene.ws()->detach( s->group(View::RENDERING) );
// erase the source from the update list & get next element
its = sources_.erase(its);
// delete the source : safe now
delete s;
}
@@ -152,6 +143,27 @@ SourceList::iterator Session::deleteSource(Source *s)
return its;
}
void Session::removeSource(Source *s)
{
// lock before change
access_.lock();
// find the source
SourceList::iterator its = find(s);
// ok, its in the list !
if (its != sources_.end()) {
// remove Node from the rendering scene
render_.scene.ws()->detach( s->group(View::RENDERING) );
// erase the source from the update list & get next element
sources_.erase(its);
}
// unlock access
access_.unlock();
}
Source *Session::popSource()
{
Source *s = nullptr;
@@ -160,10 +172,8 @@ Source *Session::popSource()
if (its != sources_.end())
{
s = *its;
// remove Node from the rendering scene
render_.scene.ws()->detatch( s->group(View::RENDERING) );
render_.scene.ws()->detach( s->group(View::RENDERING) );
// erase the source from the update list & get next element
sources_.erase(its);
}
@@ -195,25 +205,16 @@ SourceList::iterator Session::end()
return sources_.end();
}
SourceList::iterator Session::find(int index)
{
if (index<0)
return sources_.end();
int i = 0;
SourceList::iterator it = sources_.begin();
while ( i < index && it != sources_.end() ){
i++;
it++;
}
return it;
}
SourceList::iterator Session::find(Source *s)
{
return std::find(sources_.begin(), sources_.end(), s);
}
SourceList::iterator Session::find(uint64_t id)
{
return std::find_if(sources_.begin(), sources_.end(), Source::hasId(id));
}
SourceList::iterator Session::find(std::string namesource)
{
return std::find_if(sources_.begin(), sources_.end(), Source::hasName(namesource));
@@ -229,11 +230,38 @@ uint Session::numSource() const
return sources_.size();
}
std::list<uint64_t> Session::getIdList() const
{
std::list<uint64_t> idlist;
for( auto sit = sources_.begin(); sit != sources_.end(); sit++)
idlist.push_back( (*sit)->id() );
// make sure no duplicate
idlist.unique();
return idlist;
}
bool Session::empty() const
{
return sources_.empty();
}
SourceList::iterator Session::at(int index)
{
if (index<0)
return sources_.end();
int i = 0;
SourceList::iterator it = sources_.begin();
while ( i < index && it != sources_.end() ){
i++;
it++;
}
return it;
}
int Session::index(SourceList::iterator it) const
{
int index = -1;
@@ -247,53 +275,6 @@ int Session::index(SourceList::iterator it) const
return index;
}
void Session::addRecorder(Recorder *rec)
{
recorders_.push_back(rec);
}
Recorder *Session::frontRecorder()
{
if (recorders_.empty())
return nullptr;
else
return recorders_.front();
}
void Session::stopRecorders()
{
std::list<Recorder *>::iterator iter;
for (iter=recorders_.begin(); iter != recorders_.end(); )
(*iter)->stop();
}
void Session::clearRecorders()
{
std::list<Recorder *>::iterator iter;
for (iter=recorders_.begin(); iter != recorders_.end(); )
{
Recorder *rec = *iter;
rec->stop();
iter = recorders_.erase(iter);
delete rec;
}
}
void Session::transferRecorders(Session *dest)
{
if (dest == nullptr)
return;
std::list<Recorder *>::iterator iter;
for (iter=recorders_.begin(); iter != recorders_.end(); )
{
dest->recorders_.push_back(*iter);
iter = recorders_.erase(iter);
}
}
void Session::lock()
{
access_.lock();
@@ -305,25 +286,11 @@ void Session::unlock()
}
Session *loadSession_(const std::string& filename)
Session *Session::load(const std::string& filename)
{
Session *s = new Session;
SessionCreator creator;
creator.load(filename);
if (s) {
// actual loading of xml file
SessionCreator creator( s );
if (creator.load(filename)) {
// loaded ok
s->setFilename(filename);
}
else {
// error loading
delete s;
s = nullptr;
}
}
return s;
return creator.session();
}

View File

@@ -6,7 +6,7 @@
#include "View.h"
#include "Source.h"
class Recorder;
class FrameGrabber;
class Session
{
@@ -14,12 +14,18 @@ public:
Session();
~Session();
static Session *load(const std::string& filename);
// add given source into the session
SourceList::iterator addSource (Source *s);
// delete the source s from the session
SourceList::iterator deleteSource (Source *s);
// remove this source from the session
// Does not delete the source
void removeSource(Source *s);
// get ptr to front most source and remove it from the session
// Does not delete the source
Source *popSource();
@@ -29,10 +35,14 @@ public:
uint numSource() const;
SourceList::iterator begin ();
SourceList::iterator end ();
SourceList::iterator find (int index);
SourceList::iterator find (Source *s);
SourceList::iterator find (std::string name);
SourceList::iterator find (Node *node);
SourceList::iterator find (uint64_t id);
std::list<uint64_t> getIdList() const;
SourceList::iterator at (int index);
int index (SourceList::iterator it) const;
// update all sources and mark sources which failed
@@ -48,13 +58,6 @@ public:
// get frame result of render
inline FrameBuffer *frame () const { return render_.frame(); }
// Recorders
void addRecorder(Recorder *rec);
Recorder *frontRecorder();
void stopRecorders();
void clearRecorders();
void transferRecorders(Session *dest);
// configure rendering resolution
void setResolution(glm::vec3 resolution);
@@ -80,12 +83,10 @@ protected:
SourceList sources_;
std::map<View::Mode, Group*> config_;
bool active_;
std::list<Recorder *> recorders_;
std::list<FrameGrabber *> grabbers_;
float fading_target_;
std::mutex access_;
};
Session *loadSession_(const std::string& filename);
#endif // SESSION_H

View File

@@ -8,6 +8,10 @@
#include "Source.h"
#include "MediaSource.h"
#include "SessionSource.h"
#include "StreamSource.h"
#include "PatternSource.h"
#include "DeviceSource.h"
#include "NetworkSource.h"
#include "Session.h"
#include "ImageShader.h"
#include "ImageProcessingShader.h"
@@ -24,138 +28,268 @@ std::string SessionCreator::info(const std::string& filename)
XMLDocument doc;
XMLError eResult = doc.LoadFile(filename.c_str());
if ( XMLResultError(eResult))
if ( XMLResultError(eResult)) {
Log::Warning("%s could not be openned.", filename.c_str());
return ret;
}
XMLElement *header = doc.FirstChildElement(APP_NAME);
if (header != nullptr && header->Attribute("date") != 0) {
int s = header->IntAttribute("size");
ret = std::to_string( s ) + " source" + ( s > 1 ? "s\n" : "\n");
std::string date( header->Attribute("date") );
ret += date.substr(6,2) + "/" + date.substr(4,2) + "/" + date.substr(0,4) + " ";
ret += date.substr(8,2) + ":" + date.substr(10,2) + "\n";
const char *att_string = header->Attribute("resolution");
if (att_string)
ret += std::string( att_string ) + "\n";
att_string = header->Attribute("date");
if (att_string) {
std::string date( att_string );
ret += date.substr(6,2) + "/" + date.substr(4,2) + "/" + date.substr(0,4) + " @ ";
ret += date.substr(8,2) + ":" + date.substr(10,2);
}
}
return ret;
}
SessionCreator::SessionCreator(Session *session): Visitor(), session_(session)
SessionCreator::SessionCreator(): SessionLoader(nullptr)
{
}
SessionCreator::~SessionCreator()
{
}
bool SessionCreator::load(const std::string& filename)
void SessionCreator::load(const std::string& filename)
{
XMLError eResult = xmlDoc_.LoadFile(filename.c_str());
if ( XMLResultError(eResult))
return false;
if ( XMLResultError(eResult)){
Log::Warning("%s could not be openned.", filename.c_str());
return;
}
XMLElement *header = xmlDoc_.FirstChildElement(APP_NAME);
if (header == nullptr) {
Log::Warning("%s is not a %s session file.", filename.c_str(), APP_NAME);
return false;
return;
}
int version_major = -1, version_minor = -1;
header->QueryIntAttribute("major", &version_major); // TODO incompatible if major is different?
header->QueryIntAttribute("major", &version_major);
header->QueryIntAttribute("minor", &version_minor);
if (version_major != XML_VERSION_MAJOR || version_minor != XML_VERSION_MINOR){
Log::Warning("%s is in a different versions of session file. Loading might fail.", filename.c_str());
return false;
Log::Warning("%s session file is in version v%d.%d. but this vimix program expects v%d.%d.\n"
"Loading might fail or lead to different or incomplete configuration.\n"
"You can save this session again to avoid this warning.",
filename.c_str(), version_major, version_minor, XML_VERSION_MAJOR, XML_VERSION_MINOR);
// return;
}
// ok, ready to read sources
loadSession( xmlDoc_.FirstChildElement("Session") );
// excellent, session was created: load optionnal config
if (session_){
loadConfig( xmlDoc_.FirstChildElement("Views") );
}
// session file seems legit, create a session
session_ = new Session;
return true;
// ready to read sources
SessionLoader::load( xmlDoc_.FirstChildElement("Session") );
// load optionnal config
loadConfig( xmlDoc_.FirstChildElement("Views") );
// all good
session_->setFilename(filename);
}
void SessionCreator::loadSession(XMLElement *sessionNode)
{
if (sessionNode != nullptr) {
// create a session if not provided
if (!session_)
session_ = new Session;
int counter = 0;
XMLElement* sourceNode = sessionNode->FirstChildElement("Source");
for( ; sourceNode ; sourceNode = sourceNode->NextSiblingElement())
{
xmlCurrent_ = sourceNode;
counter++;
const char *pType = xmlCurrent_->Attribute("type");
if (!pType)
continue;
if ( std::string(pType) == "MediaSource") {
MediaSource *new_media_source = new MediaSource();
new_media_source->accept(*this);
session_->addSource(new_media_source);
}
else if ( std::string(pType) == "SessionSource") {
SessionSource *new_session_source = new SessionSource();
new_session_source->accept(*this);
session_->addSource(new_session_source);
}
else if ( std::string(pType) == "RenderSource") {
RenderSource *new_render_source = new RenderSource(session_);
new_render_source->accept(*this);
session_->addSource(new_render_source);
}
// TODO : create other types of source
}
// create clones after all sources to potentially clone have been created
sourceNode = sessionNode->FirstChildElement("Source");
for( ; sourceNode ; sourceNode = sourceNode->NextSiblingElement())
{
xmlCurrent_ = sourceNode;
counter++;
const char *pType = xmlCurrent_->Attribute("type");
if (!pType)
continue;
if ( std::string(pType) == "CloneSource") {
XMLElement* originNode = xmlCurrent_->FirstChildElement("origin");
if (originNode) {
std::string sourcename = std::string ( originNode->GetText() );
SourceList::iterator origin = session_->find(sourcename);
if (origin != session_->end()) {
CloneSource *new_clone_source = (*origin)->clone();
new_clone_source->accept(*this);
session_->addSource(new_clone_source);
}
}
}
}
}
else
Log::Warning("Session seems empty.");
}
void SessionCreator::loadConfig(XMLElement *viewsNode)
{
if (viewsNode != nullptr) {
// ok, ready to read views
SessionCreator::XMLToNode( viewsNode->FirstChildElement("Mixing"), *session_->config(View::MIXING));
SessionCreator::XMLToNode( viewsNode->FirstChildElement("Geometry"), *session_->config(View::GEOMETRY));
SessionCreator::XMLToNode( viewsNode->FirstChildElement("Layer"), *session_->config(View::LAYER));
SessionCreator::XMLToNode( viewsNode->FirstChildElement("Rendering"), *session_->config(View::RENDERING));
SessionLoader::XMLToNode( viewsNode->FirstChildElement("Mixing"), *session_->config(View::MIXING));
SessionLoader::XMLToNode( viewsNode->FirstChildElement("Geometry"), *session_->config(View::GEOMETRY));
SessionLoader::XMLToNode( viewsNode->FirstChildElement("Layer"), *session_->config(View::LAYER));
SessionLoader::XMLToNode( viewsNode->FirstChildElement("Appearance"), *session_->config(View::APPEARANCE));
SessionLoader::XMLToNode( viewsNode->FirstChildElement("Rendering"), *session_->config(View::RENDERING));
}
}
void SessionCreator::XMLToNode(tinyxml2::XMLElement *xml, Node &n)
SessionLoader::SessionLoader(Session *session): Visitor(), session_(session)
{
}
void SessionLoader::load(XMLElement *sessionNode)
{
sources_id_.clear();
if (sessionNode != nullptr && session_ != nullptr) {
XMLElement* sourceNode = sessionNode->FirstChildElement("Source");
for( ; sourceNode ; sourceNode = sourceNode->NextSiblingElement())
{
xmlCurrent_ = sourceNode;
// source to load
Source *load_source = nullptr;
// check if a source with the given id exists in the session
uint64_t id__ = 0;
xmlCurrent_->QueryUnsigned64Attribute("id", &id__);
SourceList::iterator sit = session_->find(id__);
// no source with this id exists
if ( sit == session_->end() ) {
// create a new source depending on type
const char *pType = xmlCurrent_->Attribute("type");
if (!pType)
continue;
if ( std::string(pType) == "MediaSource") {
load_source = new MediaSource;
}
else if ( std::string(pType) == "SessionSource") {
load_source = new SessionSource;
}
else if ( std::string(pType) == "RenderSource") {
load_source = new RenderSource(session_);
}
else if ( std::string(pType) == "PatternSource") {
load_source = new PatternSource;
}
else if ( std::string(pType) == "DeviceSource") {
load_source = new DeviceSource;
}
else if ( std::string(pType) == "NetworkSource") {
load_source = new NetworkSource;
}
// skip failed (including clones)
if (!load_source)
continue;
// add source to session
session_->addSource(load_source);
}
// get reference to the existing source
else
load_source = *sit;
// apply config to source
load_source->accept(*this);
load_source->touch();
// remember
sources_id_.push_back( load_source->id() );
}
// create clones after all sources, to be able to clone a source created above
sourceNode = sessionNode->FirstChildElement("Source");
for( ; sourceNode ; sourceNode = sourceNode->NextSiblingElement())
{
xmlCurrent_ = sourceNode;
// verify type of node
const char *pType = xmlCurrent_->Attribute("type");
if ( pType && std::string(pType) == "CloneSource") {
// check if a source with same id exists
uint64_t id__ = 0;
xmlCurrent_->QueryUnsigned64Attribute("id", &id__);
SourceList::iterator sit = session_->find(id__);
// no source clone with this id exists
if ( sit == session_->end() ) {
// clone from given origin
XMLElement* originNode = xmlCurrent_->FirstChildElement("origin");
if (originNode) {
std::string sourcename = std::string ( originNode->GetText() );
SourceList::iterator origin = session_->find(sourcename);
// found the orign source
if (origin != session_->end()) {
// create a new source of type Clone
Source *clone_source = (*origin)->clone();
// add source to session
session_->addSource(clone_source);
// apply config to source
clone_source->accept(*this);
clone_source->touch();
// remember
sources_id_.push_back( clone_source->id() );
}
}
}
}
}
// make sure no duplicate
sources_id_.unique();
}
}
Source *SessionLoader::cloneOrCreateSource(tinyxml2::XMLElement *sourceNode)
{
xmlCurrent_ = sourceNode;
// source to load
Source *load_source = nullptr;
bool is_clone = false;
// check if a source with the given id exists in the session
uint64_t id__ = 0;
xmlCurrent_->QueryUnsigned64Attribute("id", &id__);
SourceList::iterator sit = session_->find(id__);
// no source with this id exists
if ( sit == session_->end() ) {
// create a new source depending on type
const char *pType = xmlCurrent_->Attribute("type");
if (pType) {
if ( std::string(pType) == "MediaSource") {
load_source = new MediaSource;
}
else if ( std::string(pType) == "SessionSource") {
load_source = new SessionSource;
}
else if ( std::string(pType) == "RenderSource") {
load_source = new RenderSource(session_);
}
else if ( std::string(pType) == "PatternSource") {
load_source = new PatternSource;
}
else if ( std::string(pType) == "DeviceSource") {
load_source = new DeviceSource;
}
else if ( std::string(pType) == "NetworkSource") {
load_source = new NetworkSource;
}
else if ( std::string(pType) == "CloneSource") {
// clone from given origin
XMLElement* originNode = xmlCurrent_->FirstChildElement("origin");
if (originNode) {
std::string sourcename = std::string ( originNode->GetText() );
SourceList::iterator origin = session_->find(sourcename);
// found the orign source
if (origin != session_->end())
load_source = (*origin)->clone();
}
}
}
}
// clone existing source
else {
load_source = (*sit)->clone();
is_clone = true;
}
// apply config to source
if (load_source) {
load_source->accept(*this);
// reset mixing (force to place in mixing scene)
load_source->group(View::MIXING)->translation_ = glm::vec3(DEFAULT_MIXING_TRANSLATION, 0.f);
// increment depth for clones (avoid supperposition)
if (is_clone)
load_source->group(View::LAYER)->translation_.z += 0.2f;
}
return load_source;
}
void SessionLoader::XMLToNode(tinyxml2::XMLElement *xml, Node &n)
{
if (xml != nullptr){
XMLElement *node = xml->FirstChildElement("Node");
@@ -171,22 +305,29 @@ void SessionCreator::XMLToNode(tinyxml2::XMLElement *xml, Node &n)
XMLElement *rotationNode = node->FirstChildElement("rotation");
if (rotationNode)
tinyxml2::XMLElementToGLM( rotationNode->FirstChildElement("vec3"), n.rotation_);
XMLElement *cropNode = node->FirstChildElement("crop");
if (cropNode)
tinyxml2::XMLElementToGLM( cropNode->FirstChildElement("vec3"), n.crop_);
}
}
void SessionCreator::visit(Node &n)
void SessionLoader::visit(Node &n)
{
XMLToNode(xmlCurrent_, n);
}
void SessionCreator::visit(MediaPlayer &n)
void SessionLoader::visit(MediaPlayer &n)
{
XMLElement* mediaplayerNode = xmlCurrent_->FirstChildElement("MediaPlayer");
uint64_t id__ = -1;
mediaplayerNode->QueryUnsigned64Attribute("id", &id__);
if (mediaplayerNode) {
// timeline
XMLElement *timelineelement = mediaplayerNode->FirstChildElement("Timeline");
if (timelineelement) {
Timeline tl;
tl.setTiming( n.timeline()->interval(), n.timeline()->step());
XMLElement *gapselement = timelineelement->FirstChildElement("Gaps");
if (gapselement) {
XMLElement* gap = gapselement->FirstChildElement("Interval");
@@ -206,20 +347,26 @@ void SessionCreator::visit(MediaPlayer &n)
}
n.setTimeline(tl);
}
// playing properties
double speed = 1.0;
mediaplayerNode->QueryDoubleAttribute("speed", &speed);
n.setPlaySpeed(speed);
int loop = 1;
mediaplayerNode->QueryIntAttribute("loop", &loop);
n.setLoop( (MediaPlayer::LoopMode) loop);
bool play = true;
mediaplayerNode->QueryBoolAttribute("play", &play);
n.play(play);
// change play status only if different id (e.g. new media player)
if ( n.id() != id__ ) {
double speed = 1.0;
mediaplayerNode->QueryDoubleAttribute("speed", &speed);
n.setPlaySpeed(speed);
int loop = 1;
mediaplayerNode->QueryIntAttribute("loop", &loop);
n.setLoop( (MediaPlayer::LoopMode) loop);
bool play = true;
mediaplayerNode->QueryBoolAttribute("play", &play);
n.play(play);
}
}
}
void SessionCreator::visit(Shader &n)
void SessionLoader::visit(Shader &n)
{
XMLElement* color = xmlCurrent_->FirstChildElement("color");
if ( color ) {
@@ -233,7 +380,7 @@ void SessionCreator::visit(Shader &n)
}
}
void SessionCreator::visit(ImageShader &n)
void SessionLoader::visit(ImageShader &n)
{
const char *pType = xmlCurrent_->Attribute("type");
if ( std::string(pType) != "ImageShader" )
@@ -242,11 +389,29 @@ void SessionCreator::visit(ImageShader &n)
XMLElement* uniforms = xmlCurrent_->FirstChildElement("uniforms");
if (uniforms) {
uniforms->QueryFloatAttribute("stipple", &n.stipple);
uniforms->QueryUnsignedAttribute("mask", &n.mask);
}
}
void SessionCreator::visit(ImageProcessingShader &n)
void SessionLoader::visit(MaskShader &n)
{
const char *pType = xmlCurrent_->Attribute("type");
if ( std::string(pType) != "MaskShader" )
return;
xmlCurrent_->QueryUnsignedAttribute("mode", &n.mode);
xmlCurrent_->QueryUnsignedAttribute("shape", &n.shape);
XMLElement* uniforms = xmlCurrent_->FirstChildElement("uniforms");
if (uniforms) {
uniforms->QueryFloatAttribute("blur", &n.blur);
uniforms->QueryIntAttribute("option", &n.option);
XMLElement* size = uniforms->FirstChildElement("size");
if (size)
tinyxml2::XMLElementToGLM( size->FirstChildElement("vec2"), n.size);
}
}
void SessionLoader::visit(ImageProcessingShader &n)
{
const char *pType = xmlCurrent_->Attribute("type");
if ( std::string(pType) != "ImageProcessingShader" )
@@ -277,56 +442,127 @@ void SessionCreator::visit(ImageProcessingShader &n)
tinyxml2::XMLElementToGLM( chromakey->FirstChildElement("vec4"), n.chromakey);
}
void SessionCreator::visit (Source& s)
void SessionLoader::visit (Source& s)
{
XMLElement* sourceNode = xmlCurrent_;
const char *pName = sourceNode->Attribute("name");
s.setName(pName);
xmlCurrent_ = sourceNode->FirstChildElement("Mixing");
s.groupNode(View::MIXING)->accept(*this);
if (xmlCurrent_) s.groupNode(View::MIXING)->accept(*this);
xmlCurrent_ = sourceNode->FirstChildElement("Geometry");
s.groupNode(View::GEOMETRY)->accept(*this);
if (xmlCurrent_) s.groupNode(View::GEOMETRY)->accept(*this);
xmlCurrent_ = sourceNode->FirstChildElement("Layer");
s.groupNode(View::LAYER)->accept(*this);
if (xmlCurrent_) s.groupNode(View::LAYER)->accept(*this);
xmlCurrent_ = sourceNode->FirstChildElement("Appearance");
if (xmlCurrent_) s.groupNode(View::APPEARANCE)->accept(*this);
xmlCurrent_ = sourceNode->FirstChildElement("Blending");
s.blendingShader()->accept(*this);
if (xmlCurrent_) s.blendingShader()->accept(*this);
xmlCurrent_ = sourceNode->FirstChildElement("Mask");
if (xmlCurrent_) {
// read the mask shader attributes
s.maskShader()->accept(*this);
// if there is an Image mask stored
XMLElement* imageNode = xmlCurrent_->FirstChildElement("Image");
if (imageNode) {
// if there is an internal array of data
XMLElement* array = imageNode->FirstChildElement("array");
if (array) {
// create a temporary jpeg with size of the array
FrameBufferImage::jpegBuffer jpgimg;
array->QueryUnsignedAttribute("len", &jpgimg.len);
// ok, we got a size of data to load
if (jpgimg.len>0) {
// allocate jpeg buffer
jpgimg.buffer = (unsigned char*) malloc(jpgimg.len);
// actual decoding of array
if (XMLElementDecodeArray(array, jpgimg.buffer, jpgimg.len) )
// create and set the image from jpeg
s.setMask(new FrameBufferImage(jpgimg));
// free temporary buffer
if (jpgimg.buffer)
free(jpgimg.buffer);
}
}
}
}
xmlCurrent_ = sourceNode->FirstChildElement("ImageProcessing");
bool on = xmlCurrent_->BoolAttribute("enabled", true);
s.processingShader()->accept(*this);
s.setImageProcessingEnabled(on);
if (xmlCurrent_) {
bool on = xmlCurrent_->BoolAttribute("enabled", true);
s.processingShader()->accept(*this);
s.setImageProcessingEnabled(on);
}
// restore current
xmlCurrent_ = sourceNode;
}
void SessionCreator::visit (MediaSource& s)
void SessionLoader::visit (MediaSource& s)
{
// set uri
XMLElement* uriNode = xmlCurrent_->FirstChildElement("uri");
if (uriNode) {
std::string uri = std::string ( uriNode->GetText() );
s.setPath(uri);
// load only new files
if ( uri != s.path() )
s.setPath(uri);
}
// set config media player
s.mediaplayer()->accept(*this);
}
void SessionCreator::visit (SessionSource& s)
void SessionLoader::visit (SessionSource& s)
{
// set uri
XMLElement* pathNode = xmlCurrent_->FirstChildElement("path");
if (pathNode) {
std::string path = std::string ( pathNode->GetText() );
s.load(path);
// load only new files
if ( path != s.path() )
s.load(path);
}
}
void SessionLoader::visit (PatternSource& s)
{
uint t = xmlCurrent_->UnsignedAttribute("pattern");
glm::ivec2 resolution(800, 600);
XMLElement* res = xmlCurrent_->FirstChildElement("resolution");
if (res)
tinyxml2::XMLElementToGLM( res->FirstChildElement("ivec2"), resolution);
// change only if different pattern
if ( t != s.pattern()->type() )
s.setPattern(t, resolution);
}
void SessionLoader::visit (DeviceSource& s)
{
std::string devname = std::string ( xmlCurrent_->Attribute("device") );
// change only if different device
if ( devname != s.device() )
s.setDevice(devname);
}
void SessionLoader::visit (NetworkSource& s)
{
std::string connect = std::string ( xmlCurrent_->Attribute("connection") );
// change only if different device
if ( connect != s.connection() )
s.setConnection(connect);
}

View File

@@ -1,57 +1,77 @@
#ifndef SESSIONCREATOR_H
#define SESSIONCREATOR_H
#include <list>
#include "Visitor.h"
#include <tinyxml2.h>
class Session;
class SessionCreator : public Visitor {
tinyxml2::XMLDocument xmlDoc_;
tinyxml2::XMLElement *xmlCurrent_;
Session *session_;
void loadSession(tinyxml2::XMLElement *sessionNode);
void loadConfig(tinyxml2::XMLElement *viewsNode);
class SessionLoader : public Visitor {
public:
SessionCreator(Session *session = nullptr);
~SessionCreator();
bool load(const std::string& filename);
SessionLoader(Session *session);
inline Session *session() const { return session_; }
// Elements of Scene
void visit(Node& n) override;
void load(tinyxml2::XMLElement *sessionNode);
inline std::list<uint64_t> getIdList() const { return sources_id_; }
void visit(Scene& n) override {}
void visit(Group& n) override {}
void visit(Switch& n) override {}
void visit(Primitive& n) override {}
void visit(Surface& n) override {}
void visit(ImageSurface& n) override {}
void visit(MediaSurface& n) override {}
void visit(FrameBufferSurface& n) override {}
void visit(LineStrip& n) override {}
void visit(LineSquare&) override {}
void visit(LineCircle& n) override {}
void visit(Mesh& n) override {}
Source *cloneOrCreateSource(tinyxml2::XMLElement *sourceNode);
// Elements of Scene
void visit (Node& n) override;
void visit (Scene&) override {}
void visit (Group&) override {}
void visit (Switch&) override {}
void visit (Primitive&) override {}
void visit (Surface&) override {}
void visit (ImageSurface&) override {}
void visit (MediaSurface&) override {}
void visit (FrameBufferSurface&) override {}
void visit (LineStrip&) override {}
void visit (LineSquare&) override {}
void visit (LineCircle&) override {}
void visit (Mesh&) override {}
// Elements with attributes
void visit(MediaPlayer& n) override;
void visit(Shader& n) override;
void visit(ImageShader& n) override;
void visit(ImageProcessingShader& n) override;
void visit (MediaPlayer& n) override;
void visit (Shader& n) override;
void visit (ImageShader& n) override;
void visit (MaskShader& n) override;
void visit (ImageProcessingShader& n) override;
// Sources
void visit (Source& s) override;
void visit (MediaSource& s) override;
void visit (SessionSource& s) override;
void visit (PatternSource& s) override;
void visit (DeviceSource& s) override;
void visit (NetworkSource& s) override;
protected:
tinyxml2::XMLElement *xmlCurrent_;
Session *session_;
std::list<uint64_t> sources_id_;
static std::string info(const std::string& filename);
static void XMLToNode(tinyxml2::XMLElement *xml, Node &n);
};
class SessionCreator : public SessionLoader {
tinyxml2::XMLDocument xmlDoc_;
void loadConfig(tinyxml2::XMLElement *viewsNode);
public:
SessionCreator();
void load(const std::string& filename);
static std::string info(const std::string& filename);
};
#endif // SESSIONCREATOR_H

View File

@@ -10,8 +10,7 @@
#include "ImageShader.h"
#include "ImageProcessingShader.h"
#include "Resource.h"
#include "Primitives.h"
#include "Mesh.h"
#include "Decorations.h"
#include "SearchVisitor.h"
#include "Session.h"
#include "SessionCreator.h"
@@ -48,22 +47,16 @@ SessionSource::SessionSource() : Source(), path_("")
overlays_[View::TRANSITION]->attach(center);
groups_[View::TRANSITION]->attach(overlays_[View::TRANSITION]);
// set symbol
symbol_ = new Symbol(Symbol::SESSION, glm::vec3(0.8f, 0.8f, 0.01f));
failed_ = false;
wait_for_sources_ = false;
session_ = nullptr;
// create surface:
// - textured with original texture from session
// - crop & repeat UV can be managed here
// - additional custom shader can be associated
sessionsurface_ = new Surface(processingshader_);
}
SessionSource::~SessionSource()
{
// delete surface
delete sessionsurface_;
// delete session
if (session_)
delete session_;
@@ -78,7 +71,7 @@ void SessionSource::load(const std::string &p)
session_ = new Session;
else
// launch a thread to load the session file
sessionLoader_ = std::async(std::launch::async, loadSession_, path_);
sessionLoader_ = std::async(std::launch::async, Session::load, path_);
Log::Notify("Opening %s", p.c_str());
}
@@ -110,11 +103,6 @@ uint SessionSource::texture() const
return session_->frame()->texture();
}
void SessionSource::replaceRenderingShader()
{
sessionsurface_->replaceShader(renderingshader_);
}
void SessionSource::init()
{
// init is first about getting the loaded session
@@ -128,6 +116,8 @@ void SessionSource::init()
}
else {
session_->update(dt_);
if (wait_for_sources_) {
// force update of of all sources
@@ -157,12 +147,11 @@ void SessionSource::init()
// set resolution
session_->setResolution( session_->config(View::RENDERING)->scale_ );
// deep update once to draw framebuffer
View::need_deep_update_ = true;
// update to draw framebuffer
session_->update(dt_);
// get the texture index from framebuffer of session, apply it to the surface
sessionsurface_->setTextureIndex( session_->frame()->texture() );
texturesurface_->setTextureIndex( session_->frame()->texture() );
// create Frame buffer matching size of session
FrameBuffer *renderbuffer = new FrameBuffer( session_->frame()->resolution());
@@ -170,10 +159,6 @@ void SessionSource::init()
// set the renderbuffer of the source and attach rendering nodes
attach(renderbuffer);
// icon in mixing view
overlays_[View::MIXING]->attach( new Symbol(Symbol::SESSION, glm::vec3(0.8f, 0.8f, 0.01f)) );
overlays_[View::LAYER]->attach( new Symbol(Symbol::SESSION, glm::vec3(0.8f, 0.8f, 0.01f)) );
// wait for all sources to init
if (session_->numSource() > 0)
wait_for_sources_ = true;
@@ -187,8 +172,10 @@ void SessionSource::init()
if (initialized_){
// remove the loading icon
Node *loader = overlays_[View::TRANSITION]->back();
overlays_[View::TRANSITION]->detatch(loader);
overlays_[View::TRANSITION]->detach(loader);
delete loader;
// deep update to reorder
View::need_deep_update_++;
}
}
@@ -222,37 +209,21 @@ void SessionSource::update(float dt)
Source::update(dt);
}
void SessionSource::render()
{
if (!initialized_)
init();
else {
// render the sesion into frame buffer
static glm::mat4 projection = glm::ortho(-1.f, 1.f, 1.f, -1.f, -1.f, 1.f);
renderbuffer_->begin();
sessionsurface_->draw(glm::identity<glm::mat4>(), projection);
renderbuffer_->end();
}
}
void SessionSource::accept(Visitor& v)
{
Source::accept(v);
v.visit(*this);
if (!failed())
v.visit(*this);
}
RenderSource::RenderSource(Session *session) : Source(), session_(session)
{
// create surface:
sessionsurface_ = new Surface(processingshader_);
// set symbol
symbol_ = new Symbol(Symbol::RENDER, glm::vec3(0.8f, 0.8f, 0.01f));
}
RenderSource::~RenderSource()
{
// delete surface
delete sessionsurface_;
}
bool RenderSource::failed() const
{
@@ -267,22 +238,14 @@ uint RenderSource::texture() const
return session_->frame()->texture();
}
void RenderSource::replaceRenderingShader()
{
sessionsurface_->replaceShader(renderingshader_);
}
void RenderSource::init()
{
if (session_ == nullptr)
return;
if (session_ && session_->frame()->texture() != Resource::getTextureBlack()) {
FrameBuffer *fb = session_->frame();
// get the texture index from framebuffer of view, apply it to the surface
sessionsurface_->setTextureIndex( fb->texture() );
texturesurface_->setTextureIndex( fb->texture() );
// create Frame buffer matching size of output session
FrameBuffer *renderbuffer = new FrameBuffer( fb->resolution());
@@ -290,33 +253,19 @@ void RenderSource::init()
// set the renderbuffer of the source and attach rendering nodes
attach(renderbuffer);
// icon in mixing view
overlays_[View::MIXING]->attach( new Symbol(Symbol::RENDER, glm::vec3(0.8f, 0.8f, 0.01f)) );
overlays_[View::LAYER]->attach( new Symbol(Symbol::RENDER, glm::vec3(0.8f, 0.8f, 0.01f)) );
// deep update to reorder
View::need_deep_update_++;
// done init
initialized_ = true;
Log::Info("Source Render linked to session (%d x %d).", int(fb->resolution().x), int(fb->resolution().y) );
}
}
void RenderSource::render()
{
if (!initialized_)
init();
else {
// render the view into frame buffer
static glm::mat4 projection = glm::ortho(-1.f, 1.f, 1.f, -1.f, -1.f, 1.f);
renderbuffer_->begin();
sessionsurface_->draw(glm::identity<glm::mat4>(), projection);
renderbuffer_->end();
}
}
void RenderSource::accept(Visitor& v)
{
Source::accept(v);
v.visit(*this);
if (!failed())
v.visit(*this);
}

View File

@@ -14,7 +14,6 @@ public:
// implementation of source API
void update (float dt) override;
void setActive (bool on) override;
void render() override;
bool failed() const override;
uint texture() const override;
void accept (Visitor& v) override;
@@ -26,13 +25,13 @@ public:
inline std::string path() const { return path_; }
inline Session *session() const { return session_; }
glm::ivec2 icon() const override { return glm::ivec2(3, 16); }
protected:
void init() override;
void replaceRenderingShader() override;
static void loadSession(const std::string& filename, SessionSource *source);
Surface *sessionsurface_;
std::string path_;
Session *session_;
@@ -46,19 +45,17 @@ class RenderSource : public Source
{
public:
RenderSource(Session *session);
~RenderSource();
// implementation of source API
void render() override;
bool failed() const override;
uint texture() const override;
void accept (Visitor& v) override;
glm::ivec2 icon() const override { return glm::ivec2(0, 2); }
protected:
void init() override;
void replaceRenderingShader() override;
Surface *sessionsurface_;
Session *session_;
};

View File

@@ -2,11 +2,13 @@
#include "Log.h"
#include "Scene.h"
#include "Primitives.h"
#include "Mesh.h"
#include "Decorations.h"
#include "Source.h"
#include "MediaSource.h"
#include "SessionSource.h"
#include "PatternSource.h"
#include "DeviceSource.h"
#include "NetworkSource.h"
#include "ImageShader.h"
#include "ImageProcessingShader.h"
#include "MediaPlayer.h"
@@ -31,6 +33,7 @@ tinyxml2::XMLElement *SessionVisitor::NodeToXML(Node &n, tinyxml2::XMLDocument *
{
XMLElement *newelement = doc->NewElement("Node");
newelement->SetAttribute("visible", n.visible_);
newelement->SetAttribute("id", n.id());
XMLElement *scale = doc->NewElement("scale");
scale->InsertEndChild( XMLElementFromGLM(doc, n.scale_) );
@@ -44,6 +47,10 @@ tinyxml2::XMLElement *SessionVisitor::NodeToXML(Node &n, tinyxml2::XMLDocument *
rotation->InsertEndChild( XMLElementFromGLM(doc, n.rotation_) );
newelement->InsertEndChild(rotation);
XMLElement *crop = doc->NewElement("crop");
crop->InsertEndChild( XMLElementFromGLM(doc, n.crop_) );
newelement->InsertEndChild(crop);
return newelement;
}
@@ -110,7 +117,7 @@ void SessionVisitor::visit(Primitive &n)
}
void SessionVisitor::visit(Surface &n)
void SessionVisitor::visit(Surface &)
{
}
@@ -126,7 +133,7 @@ void SessionVisitor::visit(ImageSurface &n)
xmlCurrent_->InsertEndChild(image);
}
void SessionVisitor::visit(FrameBufferSurface &n)
void SessionVisitor::visit(FrameBufferSurface &)
{
// Node of a different type
xmlCurrent_->SetAttribute("type", "FrameBufferSurface");
@@ -143,6 +150,7 @@ void SessionVisitor::visit(MediaSurface &n)
void SessionVisitor::visit(MediaPlayer &n)
{
XMLElement *newelement = xmlDoc_->NewElement("MediaPlayer");
newelement->SetAttribute("id", n.id());
newelement->SetAttribute("play", n.isPlaying());
newelement->SetAttribute("loop", (int) n.loop());
newelement->SetAttribute("speed", n.playSpeed());
@@ -175,6 +183,7 @@ void SessionVisitor::visit(Shader &n)
{
// Shader of a simple type
xmlCurrent_->SetAttribute("type", "Shader");
xmlCurrent_->SetAttribute("id", n.id());
XMLElement *color = xmlDoc_->NewElement("color");
color->InsertEndChild( XMLElementFromGLM(xmlDoc_, n.color) );
@@ -190,18 +199,35 @@ void SessionVisitor::visit(ImageShader &n)
{
// Shader of a textured type
xmlCurrent_->SetAttribute("type", "ImageShader");
xmlCurrent_->SetAttribute("id", n.id());
XMLElement *uniforms = xmlDoc_->NewElement("uniforms");
uniforms->SetAttribute("stipple", n.stipple);
uniforms->SetAttribute("mask", n.mask);
xmlCurrent_->InsertEndChild(uniforms);
}
void SessionVisitor::visit(MaskShader &n)
{
// Shader of a mask type
xmlCurrent_->SetAttribute("type", "MaskShader");
xmlCurrent_->SetAttribute("id", n.id());
xmlCurrent_->SetAttribute("mode", n.mode);
xmlCurrent_->SetAttribute("shape", n.shape);
XMLElement *uniforms = xmlDoc_->NewElement("uniforms");
uniforms->SetAttribute("blur", n.blur);
uniforms->SetAttribute("option", n.option);
XMLElement *size = xmlDoc_->NewElement("size");
size->InsertEndChild( XMLElementFromGLM(xmlDoc_, n.size) );
uniforms->InsertEndChild(size);
xmlCurrent_->InsertEndChild(uniforms);
}
void SessionVisitor::visit(ImageProcessingShader &n)
{
// Shader of a textured type
xmlCurrent_->SetAttribute("type", "ImageProcessingShader");
xmlCurrent_->SetAttribute("id", n.id());
XMLElement *filter = xmlDoc_->NewElement("uniforms");
filter->SetAttribute("brightness", n.brightness);
@@ -213,7 +239,7 @@ void SessionVisitor::visit(ImageProcessingShader &n)
filter->SetAttribute("nbColors", n.nbColors);
filter->SetAttribute("invert", n.invert);
filter->SetAttribute("chromadelta", n.chromadelta);
filter->SetAttribute("filterid", n.filterid);
filter->SetAttribute("filter", n.filterid);
xmlCurrent_->InsertEndChild(filter);
XMLElement *gamma = xmlDoc_->NewElement("gamma");
@@ -263,7 +289,7 @@ void SessionVisitor::visit(LineSquare &)
}
void SessionVisitor::visit(LineCircle &n)
void SessionVisitor::visit(LineCircle &)
{
// Node of a different type
xmlCurrent_->SetAttribute("type", "LineCircle");
@@ -314,6 +340,7 @@ void SessionVisitor::visit(Scene &n)
void SessionVisitor::visit (Source& s)
{
XMLElement *sourceNode = xmlDoc_->NewElement( "Source" );
sourceNode->SetAttribute("id", s.id());
sourceNode->SetAttribute("name", s.name().c_str() );
// insert into hierarchy
@@ -331,10 +358,40 @@ void SessionVisitor::visit (Source& s)
sourceNode->InsertEndChild(xmlCurrent_);
s.groupNode(View::LAYER)->accept(*this);
xmlCurrent_ = xmlDoc_->NewElement( "Appearance" );
sourceNode->InsertEndChild(xmlCurrent_);
s.groupNode(View::APPEARANCE)->accept(*this);
xmlCurrent_ = xmlDoc_->NewElement( "Blending" );
sourceNode->InsertEndChild(xmlCurrent_);
s.blendingShader()->accept(*this);
xmlCurrent_ = xmlDoc_->NewElement( "Mask" );
sourceNode->InsertEndChild(xmlCurrent_);
s.maskShader()->accept(*this);
// if we are saving a pain mask
if (s.maskShader()->mode == MaskShader::PAINT) {
// get the mask previously stored
FrameBufferImage *img = s.getMask();
if (img != nullptr) {
// get the jpeg encoded buffer
FrameBufferImage::jpegBuffer jpgimg = img->getJpeg();
if (jpgimg.buffer != nullptr) {
// fill the xml array with jpeg buffer
XMLElement *array = XMLElementEncodeArray(xmlDoc_, jpgimg.buffer, jpgimg.len);
// free the buffer
free(jpgimg.buffer);
// if we could create the array
if (array) {
// create an Image node to store the mask image
XMLElement *imageelement = xmlDoc_->NewElement("Image");
imageelement->InsertEndChild(array);
xmlCurrent_->InsertEndChild(imageelement);
}
}
}
}
xmlCurrent_ = xmlDoc_->NewElement( "ImageProcessing" );
xmlCurrent_->SetAttribute("enabled", s.imageProcessingEnabled());
sourceNode->InsertEndChild(xmlCurrent_);
@@ -365,7 +422,7 @@ void SessionVisitor::visit (SessionSource& s)
path->InsertEndChild( text );
}
void SessionVisitor::visit (RenderSource& s)
void SessionVisitor::visit (RenderSource&)
{
xmlCurrent_->SetAttribute("type", "RenderSource");
}
@@ -379,3 +436,25 @@ void SessionVisitor::visit (CloneSource& s)
XMLText *text = xmlDoc_->NewText( s.origin()->name().c_str() );
origin->InsertEndChild( text );
}
void SessionVisitor::visit (PatternSource& s)
{
xmlCurrent_->SetAttribute("type", "PatternSource");
xmlCurrent_->SetAttribute("pattern", s.pattern()->type() );
XMLElement *resolution = xmlDoc_->NewElement("resolution");
resolution->InsertEndChild( XMLElementFromGLM(xmlDoc_, s.pattern()->resolution() ) );
xmlCurrent_->InsertEndChild(resolution);
}
void SessionVisitor::visit (DeviceSource& s)
{
xmlCurrent_->SetAttribute("type", "DeviceSource");
xmlCurrent_->SetAttribute("device", s.device().c_str() );
}
void SessionVisitor::visit (NetworkSource& s)
{
xmlCurrent_->SetAttribute("type", "NetworkSource");
xmlCurrent_->SetAttribute("connection", s.connection().c_str() );
}

View File

@@ -16,6 +16,7 @@ public:
bool recursive = false);
inline tinyxml2::XMLDocument *doc() const { return xmlDoc_; }
inline void setRoot(tinyxml2::XMLElement *root) { xmlCurrent_ = root; }
// Elements of Scene
void visit(Scene& n) override;
@@ -23,13 +24,13 @@ public:
void visit(Group& n) override;
void visit(Switch& n) override;
void visit(Primitive& n) override;
void visit(Surface& n) override;
void visit(Surface&) override;
void visit(ImageSurface& n) override;
void visit(MediaSurface& n) override;
void visit(FrameBufferSurface& n) override;
void visit(FrameBufferSurface&) override;
void visit(LineStrip& n) override;
void visit(LineSquare&) override;
void visit(LineCircle& n) override;
void visit(LineCircle&) override;
void visit(Mesh& n) override;
void visit(Frame& n) override;
@@ -37,13 +38,18 @@ public:
void visit(MediaPlayer& n) override;
void visit(Shader& n) override;
void visit(ImageShader& n) override;
void visit(MaskShader& n) override;
void visit(ImageProcessingShader& n) override;
// Sources
void visit (Source& s) override;
void visit (MediaSource& s) override;
void visit (SessionSource& s) override;
void visit (RenderSource& s) override;
void visit (RenderSource&) override;
void visit (CloneSource& s) override;
void visit (PatternSource& s) override;
void visit (DeviceSource& s) override;
void visit (NetworkSource& s) override;
static tinyxml2::XMLElement *NodeToXML(Node &n, tinyxml2::XMLDocument *doc);
};

View File

@@ -12,6 +12,7 @@ using namespace tinyxml2;
Settings::Application Settings::application;
static string settingsFilename = "";
void Settings::Save()
@@ -21,10 +22,11 @@ void Settings::Save()
xmlDoc.InsertFirstChild(pDec);
XMLElement *pRoot = xmlDoc.NewElement(application.name.c_str());
pRoot->SetAttribute("major", APP_VERSION_MAJOR);
pRoot->SetAttribute("minor", APP_VERSION_MINOR);
xmlDoc.InsertEndChild(pRoot);
string comment = "Settings for " + application.name;
comment += "Version " + std::to_string(APP_VERSION_MAJOR) + "." + std::to_string(APP_VERSION_MINOR);
XMLComment *pComment = xmlDoc.NewComment(comment.c_str());
pRoot->InsertEndChild(pComment);
@@ -58,14 +60,18 @@ void Settings::Save()
applicationNode->SetAttribute("pannel_stick", application.pannel_stick);
applicationNode->SetAttribute("smooth_transition", application.smooth_transition);
applicationNode->SetAttribute("smooth_cursor", application.smooth_cursor);
applicationNode->SetAttribute("action_history_follow_view", application.action_history_follow_view);
applicationNode->SetAttribute("accept_connections", application.accept_connections);
pRoot->InsertEndChild(applicationNode);
// Widgets
XMLElement *widgetsNode = xmlDoc.NewElement( "Widgets" );
widgetsNode->SetAttribute("preview", application.widget.preview);
widgetsNode->SetAttribute("history", application.widget.history);
widgetsNode->SetAttribute("media_player", application.widget.media_player);
widgetsNode->SetAttribute("shader_editor", application.widget.shader_editor);
widgetsNode->SetAttribute("stats", application.widget.stats);
widgetsNode->SetAttribute("stats_timer", application.widget.stats_timer);
widgetsNode->SetAttribute("stats_corner", application.widget.stats_corner);
widgetsNode->SetAttribute("logs", application.widget.logs);
widgetsNode->SetAttribute("toolbox", application.widget.toolbox);
@@ -76,6 +82,7 @@ void Settings::Save()
RenderNode->SetAttribute("vsync", application.render.vsync);
RenderNode->SetAttribute("multisampling", application.render.multisampling);
RenderNode->SetAttribute("blit", application.render.blit);
RenderNode->SetAttribute("gpu_decoding", application.render.gpu_decoding);
RenderNode->SetAttribute("ratio", application.render.ratio);
RenderNode->SetAttribute("res", application.render.res);
pRoot->InsertEndChild(RenderNode);
@@ -96,11 +103,33 @@ void Settings::Save()
TransitionNode->SetAttribute("profile", application.transition.profile);
pRoot->InsertEndChild(TransitionNode);
// Source
XMLElement *SourceConfNode = xmlDoc.NewElement( "Source" );
SourceConfNode->SetAttribute("new_type", application.source.new_type);
SourceConfNode->SetAttribute("ratio", application.source.ratio);
SourceConfNode->SetAttribute("res", application.source.res);
pRoot->InsertEndChild(SourceConfNode);
// bloc connections
{
XMLElement *connectionsNode = xmlDoc.NewElement( "Connections" );
// map<int, std::string>::iterator iter;
// for (iter=application.instance_names.begin(); iter != application.instance_names.end(); iter++)
// {
// XMLElement *connection = xmlDoc.NewElement( "Instance" );
// connection->SetAttribute("name", iter->second.c_str());
// connection->SetAttribute("id", iter->first);
// connectionsNode->InsertEndChild(connection);
// }
pRoot->InsertEndChild(connectionsNode);
}
// bloc views
{
XMLElement *viewsNode = xmlDoc.NewElement( "Views" );
// save current view only if [mixing, geometry or layers]
int v = application.current_view > 3 ? 1 : application.current_view;
// save current view only if [mixing, geometry, layers, appearance]
int v = application.current_view > 4 ? 1 : application.current_view;
viewsNode->SetAttribute("current", v);
map<int, Settings::ViewConfig>::iterator iter;
@@ -133,7 +162,7 @@ void Settings::Save()
recentsession->SetAttribute("path", application.recentSessions.path.c_str());
recentsession->SetAttribute("autoload", application.recentSessions.load_at_start);
recentsession->SetAttribute("autosave", application.recentSessions.save_on_exit);
recentsession->SetAttribute("valid", application.recentSessions.valid_file);
recentsession->SetAttribute("valid", application.recentSessions.front_is_valid);
for(auto it = application.recentSessions.filenames.begin();
it != application.recentSessions.filenames.end(); it++) {
XMLElement *fileNode = xmlDoc.NewElement("path");
@@ -174,6 +203,7 @@ void Settings::Save()
XMLError eResult = xmlDoc.SaveFile(settingsFilename.c_str());
XMLResultError(eResult);
}
void Settings::Load()
@@ -193,8 +223,15 @@ void Settings::Load()
XMLElement *pRoot = xmlDoc.FirstChildElement(application.name.c_str());
if (pRoot == nullptr) return;
if (application.name.compare( string( pRoot->Value() ) ) != 0 )
// different root name
// cancel on different root name
if (application.name.compare( string( pRoot->Value() ) ) != 0 )
return;
// cancel on different version
int version_major = -1, version_minor = -1;
pRoot->QueryIntAttribute("major", &version_major);
pRoot->QueryIntAttribute("minor", &version_minor);
if (version_major != APP_VERSION_MAJOR || version_minor != APP_VERSION_MINOR)
return;
XMLElement * applicationNode = pRoot->FirstChildElement("Application");
@@ -204,15 +241,19 @@ void Settings::Load()
applicationNode->QueryBoolAttribute("pannel_stick", &application.pannel_stick);
applicationNode->QueryBoolAttribute("smooth_transition", &application.smooth_transition);
applicationNode->QueryBoolAttribute("smooth_cursor", &application.smooth_cursor);
applicationNode->QueryBoolAttribute("action_history_follow_view", &application.action_history_follow_view);
applicationNode->QueryBoolAttribute("accept_connections", &application.accept_connections);
}
// Widgets
XMLElement * widgetsNode = pRoot->FirstChildElement("Widgets");
if (widgetsNode != nullptr) {
widgetsNode->QueryBoolAttribute("preview", &application.widget.preview);
widgetsNode->QueryBoolAttribute("history", &application.widget.history);
widgetsNode->QueryBoolAttribute("media_player", &application.widget.media_player);
widgetsNode->QueryBoolAttribute("shader_editor", &application.widget.shader_editor);
widgetsNode->QueryBoolAttribute("stats", &application.widget.stats);
widgetsNode->QueryBoolAttribute("stats_timer", &application.widget.stats_timer);
widgetsNode->QueryIntAttribute("stats_corner", &application.widget.stats_corner);
widgetsNode->QueryBoolAttribute("logs", &application.widget.logs);
widgetsNode->QueryBoolAttribute("toolbox", &application.widget.toolbox);
@@ -224,6 +265,7 @@ void Settings::Load()
rendernode->QueryIntAttribute("vsync", &application.render.vsync);
rendernode->QueryIntAttribute("multisampling", &application.render.multisampling);
rendernode->QueryBoolAttribute("blit", &application.render.blit);
rendernode->QueryBoolAttribute("gpu_decoding", &application.render.gpu_decoding);
rendernode->QueryIntAttribute("ratio", &application.render.ratio);
rendernode->QueryIntAttribute("res", &application.render.res);
}
@@ -241,6 +283,14 @@ void Settings::Load()
application.record.path = SystemToolkit::home_path();
}
// Source
XMLElement * sourceconfnode = pRoot->FirstChildElement("Source");
if (sourceconfnode != nullptr) {
sourceconfnode->QueryIntAttribute("new_type", &application.source.new_type);
sourceconfnode->QueryIntAttribute("ratio", &application.source.ratio);
sourceconfnode->QueryIntAttribute("res", &application.source.res);
}
// Transition
XMLElement * transitionnode = pRoot->FirstChildElement("Transition");
if (transitionnode != nullptr) {
@@ -303,6 +353,22 @@ void Settings::Load()
}
// bloc Connections
{
XMLElement * pElement = pRoot->FirstChildElement("Connections");
if (pElement)
{
// XMLElement* connectionNode = pElement->FirstChildElement("Instance");
// for( ; connectionNode ; connectionNode=connectionNode->NextSiblingElement())
// {
// int id = 0;
// connectionNode->QueryIntAttribute("id", &id);
// application.instance_names[id] = connectionNode->Attribute("name");
// }
}
}
// bloc history of recent
{
XMLElement * pElement = pRoot->FirstChildElement("Recent");
@@ -327,9 +393,9 @@ void Settings::Load()
}
pSession->QueryBoolAttribute("autoload", &application.recentSessions.load_at_start);
pSession->QueryBoolAttribute("autosave", &application.recentSessions.save_on_exit);
pSession->QueryBoolAttribute("valid", &application.recentSessions.valid_file);
pSession->QueryBoolAttribute("valid", &application.recentSessions.front_is_valid);
}
// recent session filenames
// recent session folders
XMLElement * pFolder = pElement->FirstChildElement("Folder");
if (pFolder)
{
@@ -363,7 +429,43 @@ void Settings::Load()
}
}
}
void Settings::Lock()
{
std::string lockfile = SystemToolkit::full_filename(SystemToolkit::settings_path(), "lock");
application.fresh_start = false;
FILE *file = fopen(lockfile.c_str(), "r");
int l = 0;
if (file) {
if ( fscanf(file, "%d", &l) < 1)
l = 0;
fclose(file);
}
// not locked or file not existing
if ( l < 1 ) {
file = fopen(lockfile.c_str(), "w");
if (file) {
fprintf(file, "1");
fclose(file);
}
application.fresh_start = true;
}
}
void Settings::Unlock()
{
std::string lockfile = SystemToolkit::full_filename(SystemToolkit::settings_path(), "lock");
FILE *file = fopen(lockfile.c_str(), "w");
if (file) {
fprintf(file, "0");
fclose(file);
}
}
@@ -378,3 +480,4 @@ void Settings::Check()
xmlDoc.Print();
}

View File

@@ -15,8 +15,10 @@ struct WidgetsConfig
{
bool stats;
int stats_corner;
bool stats_timer;
bool logs;
bool preview;
bool history;
bool media_player;
bool media_player_view;
bool shader_editor;
@@ -24,9 +26,11 @@ struct WidgetsConfig
WidgetsConfig() {
stats = false;
stats_timer = false;
stats_corner = 1;
logs = false;
preview = false;
history = false;
media_player = false;
media_player_view = true;
shader_editor = false;
@@ -77,26 +81,33 @@ struct History
{
std::string path;
std::list<std::string> filenames;
bool valid_file;
bool front_is_valid;
bool load_at_start;
bool save_on_exit;
History() {
path = IMGUI_LABEL_RECENT_FILES;
valid_file = false;
front_is_valid = false;
load_at_start = false;
save_on_exit = false;
}
void push(std::string filename) {
void push(const std::string &filename) {
if (filename.empty()) {
valid_file = false;
front_is_valid = false;
return;
}
filenames.remove(filename);
filenames.push_front(filename);
if (filenames.size() > MAX_RECENT_HISTORY)
filenames.pop_back();
valid_file = true;
front_is_valid = true;
}
void remove(const std::string &filename) {
if (filename.empty())
return;
if (filenames.front() == filename)
front_is_valid = false;
filenames.remove(filename);
}
};
@@ -125,19 +136,39 @@ struct RenderConfig
int ratio;
int res;
float fading;
bool gpu_decoding;
RenderConfig() {
blit = false;
vsync = 1; // todo GUI selection
multisampling = 2; // todo GUI selection
vsync = 1;
multisampling = 2;
ratio = 3;
res = 1;
fading = 0.0;
gpu_decoding = true;
}
};
struct SourceConfig
{
int new_type;
int ratio;
int res;
SourceConfig() {
new_type = 0;
ratio = 3;
res = 1;
}
};
struct Application
{
// instance check
bool fresh_start;
int instance_id;
// Verification
std::string name;
std::string executable;
@@ -148,6 +179,11 @@ struct Application
bool pannel_stick;
bool smooth_transition;
bool smooth_cursor;
bool action_history_follow_view;
// connection settings
bool accept_connections;
// std::map<int, std::string> instance_names;
// Settings of widgets
WidgetsConfig widget;
@@ -159,9 +195,12 @@ struct Application
// settings render
RenderConfig render;
// settings render
// settings exporters
RecordConfig record;
// settings new source
SourceConfig source;
// settings transition
TransitionConfig transition;
@@ -173,12 +212,14 @@ struct Application
History recentFolders;
History recentImport;
Application() : name(APP_NAME){
Application() : fresh_start(false), instance_id(0), name(APP_NAME), executable(APP_NAME) {
scale = 1.f;
accent_color = 0;
pannel_stick = false;
smooth_transition = true;
smooth_cursor = false;
action_history_follow_view = false;
accept_connections = false;
current_view = 1;
windows = std::vector<WindowConfig>(3);
windows[0].name = APP_NAME APP_TITLE;
@@ -197,6 +238,8 @@ extern Application application;
// Save and Load store settings in XML file
void Save();
void Load();
void Lock();
void Unlock();
void Check();
}

View File

@@ -1,5 +1,6 @@
#include "Shader.h"
#include "Resource.h"
#include "FrameBuffer.h"
#include "Log.h"
#include "Visitor.h"
#include "RenderingManager.h"
@@ -13,7 +14,7 @@
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include "GlmToolkit.h"
#include <glm/gtc/type_ptr.hpp>
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/string_cast.hpp>
@@ -119,14 +120,20 @@ void ShadingProgram::setUniform<float>(const std::string& name, float val1, floa
}
template<>
void ShadingProgram::setUniform<glm::vec4>(const std::string& name, glm::vec4 val) {
glm::vec4 v(val);
glUniform4fv(glGetUniformLocation(id_, name.c_str()), 1, glm::value_ptr(v));
void ShadingProgram::setUniform<glm::vec2>(const std::string& name, glm::vec2 val) {
glm::vec2 v(val);
glUniform2fv(glGetUniformLocation(id_, name.c_str()), 1, glm::value_ptr(v));
}
template<>
void ShadingProgram::setUniform<glm::vec3>(const std::string& name, glm::vec3 val) {
glm::vec3 v(val);
glUniform3fv(glGetUniformLocation(id_, name.c_str()), 1, glm::value_ptr(v));
}
template<>
void ShadingProgram::setUniform<glm::vec4>(const std::string& name, glm::vec4 val) {
glm::vec4 v(val);
glUniform4fv(glGetUniformLocation(id_, name.c_str()), 1, glm::value_ptr(v));
}
@@ -175,8 +182,7 @@ bool Shader::force_blending_opacity = false;
Shader::Shader() : blending(BLEND_OPACITY)
{
// create unique id
auto duration = std::chrono::high_resolution_clock::now().time_since_epoch();
id_ = std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count() % 1000000000;
id_ = GlmToolkit::uniqueId();
program_ = &simpleShadingProgram;
reset();
@@ -187,6 +193,7 @@ void Shader::operator = (const Shader &S )
{
color = S.color;
blending = S.blending;
iTransform = S.iTransform;
}
void Shader::accept(Visitor& v) {
@@ -205,9 +212,10 @@ void Shader::use()
// set uniforms
program_->setUniform("projection", projection);
program_->setUniform("modelview", modelview);
program_->setUniform("iTransform", iTransform);
program_->setUniform("color", color);
iResolution = glm::vec3( Rendering::manager().currentAttrib().viewport, 0.f);
glm::vec3 iResolution = glm::vec3( Rendering::manager().currentAttrib().viewport, 0.f);
program_->setUniform("iResolution", iResolution);
// Blending Function
@@ -236,7 +244,7 @@ void Shader::reset()
{
projection = glm::identity<glm::mat4>();
modelview = glm::identity<glm::mat4>();
iResolution = glm::vec3(1280.f, 720.f, 0.f);
iTransform = glm::identity<glm::mat4>();
color = glm::vec4(1.f, 1.f, 1.f, 1.f);
}

View File

@@ -7,6 +7,7 @@
// Forward declare classes referenced
class Visitor;
class FrameBuffer;
class ShadingProgram
{
@@ -22,13 +23,13 @@ public:
static void enduse();
private:
void checkCompileErr();
void checkLinkingErr();
void compile();
void link();
unsigned int vertex_id_, fragment_id_, id_;
std::string vertex_code_;
std::string fragment_code_;
void checkCompileErr();
void checkLinkingErr();
void compile();
void link();
unsigned int vertex_id_, fragment_id_, id_;
std::string vertex_code_;
std::string fragment_code_;
std::string vertex_file_;
std::string fragment_file_;
@@ -37,14 +38,14 @@ private:
class Shader
{
int id_;
uint64_t id_;
public:
Shader();
virtual ~Shader() {}
// unique identifyer generated at instanciation
inline int id () const { return id_; }
inline uint64_t id () const { return id_; }
virtual void use();
virtual void reset();
@@ -54,6 +55,7 @@ public:
glm::mat4 projection;
glm::mat4 modelview;
glm::mat4 iTransform;
glm::vec4 color;
typedef enum {
@@ -70,7 +72,6 @@ public:
protected:
ShadingProgram *program_;
glm::vec3 iResolution;
};

View File

@@ -1,24 +1,27 @@
#include <algorithm>
#include <locale>
#include <glm/gtc/matrix_access.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "Source.h"
#include "defines.h"
#include "FrameBuffer.h"
#include "Primitives.h"
#include "Decorations.h"
#include "Mesh.h"
#include "Resource.h"
#include "Session.h"
#include "SearchVisitor.h"
#include "ImageShader.h"
#include "ImageProcessingShader.h"
#include "SystemToolkit.h"
#include "Log.h"
#include "Mixer.h"
Source::Source() : initialized_(false), active_(true), need_update_(true)
Source::Source() : initialized_(false), active_(true), locked_(false), need_update_(true), symbol_(nullptr)
{
// create unique id
id_ = GlmToolkit::uniqueId();
sprintf(initials_, "__");
name_ = "Source";
mode_ = Source::UNINITIALIZED;
@@ -33,7 +36,7 @@ Source::Source() : initialized_(false), active_(true), need_update_(true)
groups_[View::MIXING] = new Group;
groups_[View::MIXING]->visible_ = false;
groups_[View::MIXING]->scale_ = glm::vec3(0.15f, 0.15f, 1.f);
groups_[View::MIXING]->translation_ = glm::vec3(-1.f, 1.f, 0.f);
groups_[View::MIXING]->translation_ = glm::vec3(DEFAULT_MIXING_TRANSLATION, 0.f);
frames_[View::MIXING] = new Switch;
Frame *frame = new Frame(Frame::ROUND, Frame::THIN, Frame::DROP);
@@ -71,26 +74,34 @@ Source::Source() : initialized_(false), active_(true), need_update_(true)
overlays_[View::GEOMETRY] = new Group;
overlays_[View::GEOMETRY]->translation_.z = 0.15;
overlays_[View::GEOMETRY]->visible_ = false;
handle_[Handles::RESIZE] = new Handles(Handles::RESIZE);
handle_[Handles::RESIZE]->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
handle_[Handles::RESIZE]->translation_.z = 0.1;
overlays_[View::GEOMETRY]->attach(handle_[Handles::RESIZE]);
handle_[Handles::RESIZE_H] = new Handles(Handles::RESIZE_H);
handle_[Handles::RESIZE_H]->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
handle_[Handles::RESIZE_H]->translation_.z = 0.1;
overlays_[View::GEOMETRY]->attach(handle_[Handles::RESIZE_H]);
handle_[Handles::RESIZE_V] = new Handles(Handles::RESIZE_V);
handle_[Handles::RESIZE_V]->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
handle_[Handles::RESIZE_V]->translation_.z = 0.1;
overlays_[View::GEOMETRY]->attach(handle_[Handles::RESIZE_V]);
handle_[Handles::ROTATE] = new Handles(Handles::ROTATE);
handle_[Handles::ROTATE]->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
handle_[Handles::ROTATE]->translation_.z = 0.1;
overlays_[View::GEOMETRY]->attach(handle_[Handles::ROTATE]);
handle_[Handles::SCALE] = new Handles(Handles::SCALE);
handle_[Handles::SCALE]->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
handle_[Handles::SCALE]->translation_.z = 0.1;
overlays_[View::GEOMETRY]->attach(handle_[Handles::SCALE]);
handles_[View::GEOMETRY][Handles::RESIZE] = new Handles(Handles::RESIZE);
handles_[View::GEOMETRY][Handles::RESIZE]->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
handles_[View::GEOMETRY][Handles::RESIZE]->translation_.z = 0.1;
overlays_[View::GEOMETRY]->attach(handles_[View::GEOMETRY][Handles::RESIZE]);
handles_[View::GEOMETRY][Handles::RESIZE_H] = new Handles(Handles::RESIZE_H);
handles_[View::GEOMETRY][Handles::RESIZE_H]->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
handles_[View::GEOMETRY][Handles::RESIZE_H]->translation_.z = 0.1;
overlays_[View::GEOMETRY]->attach(handles_[View::GEOMETRY][Handles::RESIZE_H]);
handles_[View::GEOMETRY][Handles::RESIZE_V] = new Handles(Handles::RESIZE_V);
handles_[View::GEOMETRY][Handles::RESIZE_V]->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
handles_[View::GEOMETRY][Handles::RESIZE_V]->translation_.z = 0.1;
overlays_[View::GEOMETRY]->attach(handles_[View::GEOMETRY][Handles::RESIZE_V]);
handles_[View::GEOMETRY][Handles::ROTATE] = new Handles(Handles::ROTATE);
handles_[View::GEOMETRY][Handles::ROTATE]->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
handles_[View::GEOMETRY][Handles::ROTATE]->translation_.z = 0.1;
overlays_[View::GEOMETRY]->attach(handles_[View::GEOMETRY][Handles::ROTATE]);
handles_[View::GEOMETRY][Handles::SCALE] = new Handles(Handles::SCALE);
handles_[View::GEOMETRY][Handles::SCALE]->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
handles_[View::GEOMETRY][Handles::SCALE]->translation_.z = 0.1;
overlays_[View::GEOMETRY]->attach(handles_[View::GEOMETRY][Handles::SCALE]);
handles_[View::GEOMETRY][Handles::MENU] = new Handles(Handles::MENU);
handles_[View::GEOMETRY][Handles::MENU]->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
handles_[View::GEOMETRY][Handles::MENU]->translation_.z = 0.1;
overlays_[View::GEOMETRY]->attach(handles_[View::GEOMETRY][Handles::MENU]);
handles_[View::GEOMETRY][Handles::CROP] = new Handles(Handles::CROP);
handles_[View::GEOMETRY][Handles::CROP]->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
handles_[View::GEOMETRY][Handles::CROP]->translation_.z = 0.1;
overlays_[View::GEOMETRY]->attach(handles_[View::GEOMETRY][Handles::CROP]);
frame = new Frame(Frame::SHARP, Frame::THIN, Frame::NONE);
frame->translation_.z = 0.1;
@@ -101,6 +112,7 @@ Source::Source() : initialized_(false), active_(true), need_update_(true)
// default layer nodes
groups_[View::LAYER] = new Group;
groups_[View::LAYER]->visible_ = false;
groups_[View::LAYER]->translation_.z = -1.f;
frames_[View::LAYER] = new Switch;
frame = new Frame(Frame::ROUND, Frame::THIN, Frame::PERSPECTIVE);
@@ -118,21 +130,101 @@ Source::Source() : initialized_(false), active_(true), need_update_(true)
overlays_[View::LAYER]->visible_ = false;
groups_[View::LAYER]->attach(overlays_[View::LAYER]);
// default appearance node
groups_[View::APPEARANCE] = new Group;
groups_[View::APPEARANCE]->visible_ = false;
frames_[View::APPEARANCE] = new Switch;
frame = new Frame(Frame::SHARP, Frame::THIN, Frame::NONE);
frame->translation_.z = 0.1;
frame->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 0.7f);
frames_[View::APPEARANCE]->attach(frame);
frame = new Frame(Frame::SHARP, Frame::LARGE, Frame::NONE);
frame->translation_.z = 0.1;
frame->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 1.f);
frames_[View::APPEARANCE]->attach(frame);
groups_[View::APPEARANCE]->attach(frames_[View::APPEARANCE]);
overlays_[View::APPEARANCE] = new Group;
overlays_[View::APPEARANCE]->translation_.z = 0.1;
overlays_[View::APPEARANCE]->visible_ = false;
handles_[View::APPEARANCE][Handles::RESIZE] = new Handles(Handles::RESIZE);
handles_[View::APPEARANCE][Handles::RESIZE]->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 1.f);
handles_[View::APPEARANCE][Handles::RESIZE]->translation_.z = 0.1;
overlays_[View::APPEARANCE]->attach(handles_[View::APPEARANCE][Handles::RESIZE]);
handles_[View::APPEARANCE][Handles::RESIZE_H] = new Handles(Handles::RESIZE_H);
handles_[View::APPEARANCE][Handles::RESIZE_H]->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 1.f);
handles_[View::APPEARANCE][Handles::RESIZE_H]->translation_.z = 0.1;
overlays_[View::APPEARANCE]->attach(handles_[View::APPEARANCE][Handles::RESIZE_H]);
handles_[View::APPEARANCE][Handles::RESIZE_V] = new Handles(Handles::RESIZE_V);
handles_[View::APPEARANCE][Handles::RESIZE_V]->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 1.f);
handles_[View::APPEARANCE][Handles::RESIZE_V]->translation_.z = 0.1;
overlays_[View::APPEARANCE]->attach(handles_[View::APPEARANCE][Handles::RESIZE_V]);
handles_[View::APPEARANCE][Handles::ROTATE] = new Handles(Handles::ROTATE);
handles_[View::APPEARANCE][Handles::ROTATE]->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 1.f);
handles_[View::APPEARANCE][Handles::ROTATE]->translation_.z = 0.1;
overlays_[View::APPEARANCE]->attach(handles_[View::APPEARANCE][Handles::ROTATE]);
handles_[View::APPEARANCE][Handles::SCALE] = new Handles(Handles::SCALE);
handles_[View::APPEARANCE][Handles::SCALE]->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 1.f);
handles_[View::APPEARANCE][Handles::SCALE]->translation_.z = 0.1;
overlays_[View::APPEARANCE]->attach(handles_[View::APPEARANCE][Handles::SCALE]);
handles_[View::APPEARANCE][Handles::MENU] = new Handles(Handles::MENU);
handles_[View::APPEARANCE][Handles::MENU]->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 1.f);
handles_[View::APPEARANCE][Handles::MENU]->translation_.z = 0.1;
overlays_[View::APPEARANCE]->attach(handles_[View::APPEARANCE][Handles::MENU]);
groups_[View::APPEARANCE]->attach(overlays_[View::APPEARANCE]);
// empty transition node
groups_[View::TRANSITION] = new Group;
//
// shared locker symbol
//
locker_ = new Symbol(Symbol::LOCK, glm::vec3(0.8f, -0.8f, 0.01f));
locker_->color = glm::vec4(1.f, 1.f, 1.f, 0.6f);
// add semi transparent icon statically to mixing and layer views
Group *lockgroup = new Group;
lockgroup->translation_.z = 0.1;
lockgroup->attach( locker_ );
groups_[View::LAYER]->attach(lockgroup);
groups_[View::MIXING]->attach(lockgroup);
// add semi transparent icon dynamically with overlay
overlays_[View::LAYER]->attach( locker_ );
overlays_[View::MIXING]->attach( locker_ );
// create objects
stored_status_ = new Group;
// those will be associated to nodes later
// simple image shader (with texturing) for blending
blendingshader_ = new ImageShader;
// mask produced by dedicated shader
maskshader_ = new MaskShader;
masksurface_ = new Surface(maskshader_);
// filtered image shader (with texturing and processing) for rendering
processingshader_ = new ImageProcessingShader;
// default to image processing enabled
// default rendering with image processing enabled
renderingshader_ = (Shader *) processingshader_;
// for drawing in mixing view
mixingshader_ = new ImageShader;
mixingshader_->stipple = 1.0;
// create media surface:
// - textured with original texture from media player
// - crop & repeat UV can be managed here
// - additional custom shader can be associated
texturesurface_ = new Surface(renderingshader_);
// will be created at init
renderbuffer_ = nullptr;
rendersurface_ = nullptr;
mixingsurface_ = nullptr;
maskbuffer_ = nullptr;
maskimage_ = nullptr;
mask_need_update_ = false;
}
@@ -147,6 +239,10 @@ Source::~Source()
delete stored_status_;
if (renderbuffer_)
delete renderbuffer_;
if (maskbuffer_)
delete maskbuffer_;
if (maskimage_)
delete maskimage_;
// all groups and their children are deleted in the scene
// this includes rendersurface_, overlays, blendingshader_ and rendershader_
@@ -154,6 +250,7 @@ Source::~Source()
delete groups_[View::MIXING];
delete groups_[View::GEOMETRY];
delete groups_[View::LAYER];
delete groups_[View::APPEARANCE];
delete groups_[View::TRANSITION];
groups_.clear();
@@ -164,14 +261,16 @@ Source::~Source()
// could be created but not used
if ( renderingshader_ != processingshader_ )
delete processingshader_;
delete texturesurface_;
}
void Source::setName (const std::string &name)
{
name_ = name;
name_ = SystemToolkit::transliterate(name);
initials_[0] = std::toupper( name_.front() );
initials_[1] = std::toupper( name_.back() );
initials_[0] = std::toupper( name_.front(), std::locale("C") );
initials_[1] = std::toupper( name_.back(), std::locale("C") );
}
void Source::accept(Visitor& v)
@@ -179,7 +278,6 @@ void Source::accept(Visitor& v)
v.visit(*this);
}
Source::Mode Source::mode() const
{
return mode_;
@@ -193,16 +291,19 @@ void Source::setMode(Source::Mode m)
(*g).second->visible_ = true;
}
// choose frame if selected
// choose frame 0 if visible, 1 if selected
uint index_frame = m == Source::VISIBLE ? 0 : 1;
for (auto f = frames_.begin(); f != frames_.end(); f++)
(*f).second->setActive(index_frame);
// show overlay if current
bool current = m == Source::CURRENT;
bool current = m >= Source::CURRENT;
for (auto o = overlays_.begin(); o != overlays_.end(); o++)
(*o).second->visible_ = current;
// show in appearance view if current
groups_[View::APPEARANCE]->visible_ = m > Source::VISIBLE;
mode_ = m;
}
@@ -235,7 +336,7 @@ void Source::setImageProcessingEnabled (bool on)
// apply to nodes in subclasses
// this calls replaceShader() on the Primitive and
// will delete the previously attached shader
replaceRenderingShader();
texturesurface_->replaceShader(renderingshader_);
}
bool Source::imageProcessingEnabled()
@@ -243,69 +344,108 @@ bool Source::imageProcessingEnabled()
return ( renderingshader_ == processingshader_ );
}
void Source::render()
{
if (!initialized_)
init();
else {
// render the view into frame buffer
renderbuffer_->begin();
texturesurface_->draw(glm::identity<glm::mat4>(), renderbuffer_->projection());
renderbuffer_->end();
}
}
void Source::attach(FrameBuffer *renderbuffer)
{
renderbuffer_ = renderbuffer;
// if a symbol is available, add it to overlay
if (symbol_) {
overlays_[View::MIXING]->attach( symbol_ );
overlays_[View::LAYER]->attach( symbol_ );
}
// create the surfaces to draw the frame buffer in the views
rendersurface_ = new FrameBufferSurface(renderbuffer_, blendingshader_);
groups_[View::RENDERING]->attach(rendersurface_);
groups_[View::GEOMETRY]->attach(rendersurface_);
groups_[View::MIXING]->attach(rendersurface_);
// groups_[View::LAYER]->attach(rendersurface_);
// for mixing and layer views, add another surface to overlay
// (stippled view on top with transparency)
Surface *surfacemix = new FrameBufferSurface(renderbuffer_);
ImageShader *is = static_cast<ImageShader *>(surfacemix->shader());
if (is) is->stipple = 1.0;
groups_[View::MIXING]->attach(surfacemix);
groups_[View::LAYER]->attach(surfacemix);
mixingsurface_ = new FrameBufferSurface(renderbuffer_, mixingshader_);
groups_[View::MIXING]->attach(mixingsurface_);
groups_[View::LAYER]->attach(mixingsurface_);
// scale all icon nodes to match aspect ratio of the media
NodeSet::iterator node;
for (node = groups_[View::MIXING]->begin();
node != groups_[View::MIXING]->end(); node++) {
(*node)->scale_.x = renderbuffer_->aspectRatio();
}
for (node = groups_[View::GEOMETRY]->begin();
node != groups_[View::GEOMETRY]->end(); node++) {
(*node)->scale_.x = renderbuffer_->aspectRatio();
}
for (node = groups_[View::LAYER]->begin();
node != groups_[View::LAYER]->end(); node++) {
(*node)->scale_.x = renderbuffer_->aspectRatio();
}
// for views showing a scaled mixing surface, a dedicated transparent surface allows grabbing
Surface *surfacetmp = new Surface();
surfacetmp->setTextureIndex(Resource::getTextureTransparent());
groups_[View::APPEARANCE]->attach(surfacetmp);
groups_[View::MIXING]->attach(surfacetmp);
groups_[View::LAYER]->attach(surfacetmp);
// Transition group node is optionnal
if ( groups_[View::TRANSITION]->numChildren() > 0 ) {
groups_[View::TRANSITION]->attach(rendersurface_);
groups_[View::TRANSITION]->attach(surfacemix);
for (NodeSet::iterator node = groups_[View::TRANSITION]->begin();
node != groups_[View::TRANSITION]->end(); node++) {
if (groups_[View::TRANSITION]->numChildren() > 0)
groups_[View::TRANSITION]->attach(mixingsurface_);
// scale all icon nodes to match aspect ratio
for (int v = View::MIXING; v < View::INVALID; v++) {
NodeSet::iterator node;
for (node = groups_[(View::Mode) v]->begin();
node != groups_[(View::Mode) v]->end(); node++) {
(*node)->scale_.x = renderbuffer_->aspectRatio();
}
}
// hack to place the symbols in the corner independently of aspect ratio
symbol_->translation_.x += 0.1f * (renderbuffer_->aspectRatio()-1.f);
locker_->translation_.x += 0.1f * (renderbuffer_->aspectRatio()-1.f);
// (re) create the masking buffer
if (maskbuffer_)
delete maskbuffer_;
maskbuffer_ = new FrameBuffer( glm::vec3(0.5) * renderbuffer->resolution() );
// make the source visible
if ( mode_ == UNINITIALIZED )
setMode(VISIBLE);
// request update
need_update_ = true;
}
void Source::setActive (bool on)
{
active_ = on;
// do not disactivate if a clone depends on it
for(auto clone = clones_.begin(); clone != clones_.end(); clone++) {
if ( (*clone)->active() )
active_ = true;
}
// an inactive source is visible only in the MIXING view
groups_[View::RENDERING]->visible_ = active_;
groups_[View::GEOMETRY]->visible_ = active_;
groups_[View::LAYER]->visible_ = active_;
}
void Source::setLocked (bool on)
{
locked_ = on;
// the lock icon is visible when locked
locker_->visible_ = on;
// a locked source is not visible in the GEOMETRY view (that's the whole point of it!)
groups_[View::GEOMETRY]->visible_ = !locked_;
}
// Transfer functions from coordinates to alpha (1 - transparency)
float linear_(float x, float y) {
return 1.f - CLAMP( sqrt( ( x * x ) + ( y * y ) ), 0.f, 1.f );
@@ -316,7 +456,9 @@ float quad_(float x, float y) {
}
float sin_quad(float x, float y) {
return 0.5f + 0.5f * cos( M_PI * CLAMP( ( ( x * x ) + ( y * y ) ), 0.f, 1.f ) );
float D = sqrt( ( x * x ) + ( y * y ) );
return 0.5f + 0.5f * cos( M_PI * CLAMP( D * sqrt(D), 0.f, 1.f ) );
// return 0.5f + 0.5f * cos( M_PI * CLAMP( ( ( x * x ) + ( y * y ) ), 0.f, 1.f ) );
}
void Source::update(float dt)
@@ -325,40 +467,109 @@ void Source::update(float dt)
dt_ = dt;
// update nodes if needed
if (need_update_)
if (renderbuffer_ && mixingsurface_ && maskbuffer_ && need_update_)
{
// Log::Info("UPDATE %s %f", initials_, dt);
// ADJUST alpha based on MIXING node
// read position of the mixing node and interpret this as transparency of render output
glm::vec2 dist = glm::vec2(groups_[View::MIXING]->translation_);
// use the prefered transfer function
blendingshader_->color.a = sin_quad( dist.x, dist.y );
// use the sinusoidal transfer function
blendingshader_->color = glm::vec4(1.0, 1.0, 1.0, sin_quad( dist.x, dist.y ));
mixingshader_->color = blendingshader_->color;
// CHANGE update status based on limbo
bool a = glm::length(dist) < 1.3f;
bool a = glm::length(dist) < MIXING_LIMBO_SCALE;
setActive( a );
groups_[View::MIXING]->scale_ = glm::vec3(0.15f, 0.15f, 1.f) - ( a ? glm::vec3(0.f, 0.f, 0.f) : glm::vec3(0.03f, 0.03f, 0.f) );
// TODO : find a use for a dynamic scaling of mixing source?
// groups_[View::MIXING]->scale_ = glm::vec3(0.15f, 0.15f, 1.f) - glm::vec3(0.03 * blendingshader_->color.a, 0.03 * blendingshader_->color.a, 0.f);
// adjust scale of mixing icon : smaller if not active
groups_[View::MIXING]->scale_ = glm::vec3(MIXING_ICON_SCALE) - ( a ? glm::vec3(0.f, 0.f, 0.f) : glm::vec3(0.03f, 0.03f, 0.f) );
// MODIFY geometry based on GEOMETRY node
groups_[View::RENDERING]->translation_ = groups_[View::GEOMETRY]->translation_;
groups_[View::RENDERING]->rotation_ = groups_[View::GEOMETRY]->rotation_;
// avoid any null scale
glm::vec3 s = groups_[View::GEOMETRY]->scale_;
// avoid any null scale
s.x = CLAMP_SCALE(s.x);
s.y = CLAMP_SCALE(s.y);
s.z = 1.f;
groups_[View::GEOMETRY]->scale_ = s;
groups_[View::RENDERING]->scale_ = s;
// MODIFY CROP projection based on GEOMETRY crop
renderbuffer_->setProjectionArea( glm::vec2(groups_[View::GEOMETRY]->crop_) );
// Mixing and layer icons scaled based on GEOMETRY crop
mixingsurface_->scale_ = groups_[View::GEOMETRY]->crop_;
mixingsurface_->scale_.x *= renderbuffer_->aspectRatio();
mixingsurface_->update(dt_);
// Layers icons are displayed in Perspective (diagonal)
groups_[View::LAYER]->translation_.y = groups_[View::LAYER]->translation_.x / LAYER_PERSPECTIVE;
// CHANGE lock based on range of layers stage
bool l = (groups_[View::LAYER]->translation_.x < -FOREGROUND_DEPTH)
|| (groups_[View::LAYER]->translation_.x > -BACKGROUND_DEPTH);
setLocked( l );
// adjust position of layer icon: step up when on stage
if (groups_[View::LAYER]->translation_.x < -FOREGROUND_DEPTH)
groups_[View::LAYER]->translation_.y -= 0.3f;
else if (groups_[View::LAYER]->translation_.x < -BACKGROUND_DEPTH)
groups_[View::LAYER]->translation_.y -= 0.15f;
// MODIFY depth based on LAYER node
groups_[View::MIXING]->translation_.z = groups_[View::LAYER]->translation_.z;
groups_[View::GEOMETRY]->translation_.z = groups_[View::LAYER]->translation_.z;
groups_[View::RENDERING]->translation_.z = groups_[View::LAYER]->translation_.z;
// MODIFY texture projection based on APPEARANCE node
// UV to node coordinates
static glm::mat4 UVtoScene = GlmToolkit::transform(glm::vec3(1.f, -1.f, 0.f),
glm::vec3(0.f, 0.f, 0.f),
glm::vec3(-2.f, 2.f, 1.f));
// Aspect Ratio correction transform : coordinates of Appearance Frame are scaled by render buffer width
glm::mat4 Ar = glm::scale(glm::identity<glm::mat4>(), glm::vec3(renderbuffer_->aspectRatio(), 1.f, 1.f) );
// Translation : same as Appearance Frame (modified by Ar)
glm::mat4 Tra = glm::translate(glm::identity<glm::mat4>(), groups_[View::APPEARANCE]->translation_);
// Scaling : inverse scaling (larger UV when smaller Appearance Frame)
glm::mat4 Sca = glm::scale(glm::identity<glm::mat4>(), glm::vec3(groups_[View::APPEARANCE]->scale_.x,groups_[View::APPEARANCE]->scale_.y, 1.f));
// Rotation : same angle than Appearance Frame, inverted axis
glm::mat4 Rot = glm::rotate(glm::identity<glm::mat4>(), groups_[View::APPEARANCE]->rotation_.z, glm::vec3(0.f, 0.f, -1.f) );
// Combine transformations (non transitive) in this order:
// 1. switch to Scene coordinate system
// 2. Apply the aspect ratio correction
// 3. Apply the translation
// 4. Apply the rotation (centered after translation)
// 5. Revert aspect ration correction
// 6. Apply the Scaling (independent of aspect ratio)
// 7. switch back to UV coordinate system
texturesurface_->shader()->iTransform = glm::inverse(UVtoScene) * glm::inverse(Sca) * glm::inverse(Ar) * Rot * Tra * Ar * UVtoScene;
// if a mask image was given to be updated
if (mask_need_update_) {
// fill the mask buffer (once)
if (maskbuffer_->fill(maskimage_) )
mask_need_update_ = false;
}
// otherwise, render the mask buffer
else
{
// draw mask in mask frame buffer
maskbuffer_->begin(false);
// loopback maskbuffer texture for painting
masksurface_->setTextureIndex(maskbuffer_->texture());
// fill surface with mask texture
masksurface_->draw(glm::identity<glm::mat4>(), maskbuffer_->projection());
maskbuffer_->end();
}
// set the rendered mask as mask for blending
blendingshader_->mask_texture = maskbuffer_->texture();
// do not update next frame
need_update_ = false;
}
}
FrameBuffer *Source::frame() const
@@ -383,6 +594,49 @@ bool Source::contains(Node *node) const
}
void Source::storeMask(FrameBufferImage *img)
{
// free the output mask storage
if (maskimage_ != nullptr) {
delete maskimage_;
maskimage_ = nullptr;
}
// if no image is provided
if (img == nullptr) {
// if ready
if (maskbuffer_!=nullptr) {
// get & store image from mask buffer
maskimage_ = maskbuffer_->image();
}
}
else
// store the given image
maskimage_ = img;
// maskimage_ can now be accessed with Source::getStoredMask
}
void Source::setMask(FrameBufferImage *img)
{
// if a valid image is given
if (img != nullptr && img->width>0 && img->height>0) {
// remember this new image as the current mask
// NB: will be freed when replaced
storeMask(img);
// ask Source::update to use it at next update for filling mask buffer
mask_need_update_ = true;
// ask to update the source
touch();
}
else
mask_need_update_ = false;
}
bool Source::hasNode::operator()(const Source* elem) const
{
if (_n && elem)
@@ -422,17 +676,14 @@ CloneSource *Source::clone()
CloneSource::CloneSource(Source *origin) : Source(), origin_(origin)
{
// create surface:
clonesurface_ = new Surface(renderingshader_);
// set symbol
symbol_ = new Symbol(Symbol::CLONE, glm::vec3(0.8f, 0.8f, 0.01f));
}
CloneSource::~CloneSource()
{
if (origin_)
origin_->clones_.remove(this);
// delete surface
delete clonesurface_;
}
CloneSource *CloneSource::clone()
@@ -444,17 +695,12 @@ CloneSource *CloneSource::clone()
return nullptr;
}
void CloneSource::replaceRenderingShader()
{
clonesurface_->replaceShader(renderingshader_);
}
void CloneSource::init()
{
if (origin_ && origin_->ready()) {
// get the texture index from framebuffer of view, apply it to the surface
clonesurface_->setTextureIndex( origin_->texture() );
texturesurface_->setTextureIndex( origin_->texture() );
// create Frame buffer matching size of session
FrameBuffer *renderbuffer = new FrameBuffer( origin_->frame()->resolution(), true);
@@ -462,14 +708,12 @@ void CloneSource::init()
// set the renderbuffer of the source and attach rendering nodes
attach(renderbuffer);
// icon in mixing view
overlays_[View::MIXING]->attach( new Symbol(Symbol::CLONE, glm::vec3(0.8f, 0.8f, 0.01f)) );
overlays_[View::LAYER]->attach( new Symbol(Symbol::CLONE, glm::vec3(0.8f, 0.8f, 0.01f)) );
// deep update to reorder
View::need_deep_update_++;
// done init
initialized_ = true;
Log::Info("Source Clone %s linked to source %s.", name().c_str(), origin_->name().c_str() );
Log::Info("Source %s cloning source %s.", name().c_str(), origin_->name().c_str() );
}
}
@@ -494,22 +738,10 @@ uint CloneSource::texture() const
return Resource::getTextureBlack();
}
void CloneSource::render()
{
if (!initialized_)
init();
else if (origin_) {
// render the view into frame buffer
static glm::mat4 projection = glm::ortho(-1.f, 1.f, 1.f, -1.f, -1.f, 1.f);
renderbuffer_->begin();
clonesurface_->draw(glm::identity<glm::mat4>(), projection);
renderbuffer_->end();
}
}
void CloneSource::accept(Visitor& v)
{
Source::accept(v);
v.visit(*this);
if (!failed())
v.visit(*this);
}

View File

@@ -7,20 +7,20 @@
#include <list>
#include "View.h"
#include "Decorations.h"
#define DEFAULT_MIXING_TRANSLATION -1.f, 1.f
class ImageShader;
class MaskShader;
class ImageProcessingShader;
class FrameBuffer;
class FrameBufferSurface;
class MediaPlayer;
class Surface;
class Session;
class Frame;
class Source;
class Handles;
class Symbol;
class CloneSource;
typedef std::list<Source *> SourceList;
typedef std::list<CloneSource *> CloneList;
class Source
@@ -30,13 +30,17 @@ class Source
friend class MixingView;
friend class GeometryView;
friend class LayerView;
friend class AppearanceView;
friend class TransitionView;
public:
// create a source and add it to the list
// only subclasses of sources can actually be instanciated
Source();
virtual ~Source();
Source ();
virtual ~Source ();
// Get unique id
inline uint64_t id () const { return id_; }
// manipulate name of source
void setName (const std::string &name);
@@ -63,28 +67,30 @@ public:
// tests if a given node is part of the source
bool contains (Node *node) const;
// a Source has a shader used to render in fbo
inline Shader *renderingShader() const { return renderingshader_; }
// the rendering shader always have an image processing shader
inline ImageProcessingShader *processingShader () const { return processingshader_; }
// the image processing shader can be enabled or disabled
// (NB: when disabled, a simple ImageShader is applied)
void setImageProcessingEnabled (bool on);
bool imageProcessingEnabled();
bool imageProcessingEnabled ();
// a Source has a shader to control mixing effects
inline ImageShader *blendingShader () const { return blendingshader_; }
// a Source has a shader used to render in fbo
inline Shader *renderingShader () const { return renderingshader_; }
// every Source has a frame buffer from the renderbuffer
virtual FrameBuffer *frame () const;
// a Source has a shader used to render mask
inline MaskShader *maskShader () const { return maskshader_; }
// touch to request update
inline void touch () { need_update_ = true; }
// informs if its ready (i.e. initialized)
inline bool ready() const { return initialized_; }
inline bool ready () const { return initialized_; }
// a Source shall be updated before displayed (Mixing, Geometry and Layer)
virtual void update (float dt);
@@ -93,18 +99,27 @@ public:
virtual void setActive (bool on);
inline bool active () { return active_; }
// lock mode
virtual void setLocked (bool on);
inline bool locked () { return locked_; }
// a Source shall informs if the source failed (i.e. shall be deleted)
virtual bool failed() const = 0;
virtual bool failed () const = 0;
// a Source shall define a way to get a texture
virtual uint texture() const = 0;
virtual uint texture () const = 0;
// a Source shall define how to render into the frame buffer
virtual void render() = 0;
virtual void render ();
// accept all kind of visitors
virtual void accept (Visitor& v);
// operations on mask
void storeMask(FrameBufferImage *img = nullptr);
FrameBufferImage *getMask() const { return maskimage_; }
void setMask(FrameBufferImage *img);
struct hasNode: public std::unary_function<Source*, bool>
{
bool operator()(const Source* elem) const;
@@ -123,11 +138,23 @@ public:
std::string _n;
};
struct hasId: public std::unary_function<Source*, bool>
{
inline bool operator()(const Source* elem) const {
return (elem && elem->id() == _id);
}
hasId(uint64_t id) : _id(id) { }
private:
uint64_t _id;
};
virtual glm::ivec2 icon () const { return glm::ivec2(12, 11); }
protected:
// name
std::string name_;
char initials_[3];
uint64_t id_;
// every Source shall be initialized on first draw
bool initialized_;
@@ -144,17 +171,27 @@ protected:
// the rendersurface draws the renderbuffer in the scene
// It is associated to the rendershader for mixing effects
FrameBufferSurface *rendersurface_;
FrameBufferSurface *mixingsurface_;
// image processing shaders
ImageProcessingShader *processingshader_;
// pointer to the currently attached shader
// (will be processingshader_ if image processing is enabled)
Shader *renderingshader_;
// every sub class will attach the shader to a different node / hierarchy
virtual void replaceRenderingShader() = 0;
// blendingshader provides mixing controls
ImageShader *blendingshader_;
ImageShader *mixingshader_;
// shader and buffer to draw mask
MaskShader *maskshader_;
FrameBuffer *maskbuffer_;
Surface *masksurface_;
bool mask_need_update_;
FrameBufferImage *maskimage_;
// surface to draw on
Surface *texturesurface_;
// mode for display
Mode mode_;
@@ -162,10 +199,12 @@ protected:
// overlays and frames to be displayed on top of source
std::map<View::Mode, Group*> overlays_;
std::map<View::Mode, Switch*> frames_;
Handles *handle_[5];
std::map<View::Mode, Handles*[7]> handles_;
Symbol *symbol_, *locker_;
// update
bool active_;
bool locked_;
bool need_update_;
float dt_;
Group *stored_status_;
@@ -185,7 +224,6 @@ public:
// implementation of source API
void setActive (bool on) override;
void render() override;
uint texture() const override;
bool failed() const override { return origin_ == nullptr; }
void accept (Visitor& v) override;
@@ -194,13 +232,13 @@ public:
inline void detach() { origin_ = nullptr; }
inline Source *origin() const { return origin_; }
glm::ivec2 icon() const override { return glm::ivec2(9, 2); }
protected:
// only Source class can create new CloneSource via clone();
CloneSource(Source *origin);
void init() override;
void replaceRenderingShader() override;
Surface *clonesurface_;
Source *origin_;
};

740
Stream.cpp Normal file
View File

@@ -0,0 +1,740 @@
#include <thread>
using namespace std;
// Desktop OpenGL function loader
#include <glad/glad.h>
// vmix
#include "defines.h"
#include "Log.h"
#include "Resource.h"
#include "Visitor.h"
#include "SystemToolkit.h"
#include "GlmToolkit.h"
#include "Stream.h"
#ifndef NDEBUG
#define STREAM_DEBUG
#endif
#define USE_GST_APPSINK_CALLBACKS_
Stream::Stream()
{
// create unique id
id_ = GlmToolkit::uniqueId();
description_ = "undefined";
pipeline_ = nullptr;
width_ = -1;
height_ = -1;
single_frame_ = false;
live_ = false;
ready_ = false;
failed_ = false;
enabled_ = true;
desired_state_ = GST_STATE_PAUSED;
// start index in frame_ stack
write_index_ = 0;
last_index_ = 0;
// no PBO by default
pbo_[0] = pbo_[1] = 0;
pbo_size_ = 0;
pbo_index_ = 0;
pbo_next_index_ = 0;
// OpenGL texture
textureindex_ = 0;
}
Stream::~Stream()
{
close();
}
void Stream::accept(Visitor& v) {
v.visit(*this);
}
guint Stream::texture() const
{
if (textureindex_ == 0)
return Resource::getTextureBlack();
return textureindex_;
}
void Stream::open(const std::string &gstreamer_description, int w, int h)
{
// set gstreamer pipeline source
description_ = gstreamer_description;
width_ = w;
height_ = h;
// close before re-openning
if (isOpen())
close();
execute_open();
}
std::string Stream::description() const
{
return description_;
}
void Stream::execute_open()
{
// reset
ready_ = false;
// Add custom app sink to the gstreamer pipeline
string description = description_;
description += " ! appsink name=sink";
// parse pipeline descriptor
GError *error = NULL;
pipeline_ = gst_parse_launch (description.c_str(), &error);
if (error != NULL) {
Log::Warning("Stream %s Could not construct pipeline %s:\n%s", std::to_string(id_).c_str(), description.c_str(), error->message);
g_clear_error (&error);
failed_ = true;
return;
}
g_object_set(G_OBJECT(pipeline_), "name", std::to_string(id_).c_str(), NULL);
// GstCaps *caps = gst_static_caps_get (&frame_render_caps);
string capstring = "video/x-raw,format=RGBA,width="+ std::to_string(width_) +
",height=" + std::to_string(height_);
GstCaps *caps = gst_caps_from_string(capstring.c_str());
if (!caps || !gst_video_info_from_caps (&v_frame_video_info_, caps)) {
Log::Warning("Stream %d Could not configure video frame info", id_);
failed_ = true;
return;
}
// setup appsink
GstElement *sink = gst_bin_get_by_name (GST_BIN (pipeline_), "sink");
if (!sink) {
Log::Warning("Stream %s Could not configure sink", std::to_string(id_).c_str());
failed_ = true;
return;
}
// instruct sink to use the required caps
gst_app_sink_set_caps (GST_APP_SINK(sink), caps);
// Instruct appsink to drop old buffers when the maximum amount of queued buffers is reached.
gst_app_sink_set_max_buffers( GST_APP_SINK(sink), 30);
gst_app_sink_set_drop (GST_APP_SINK(sink), true);
#ifdef USE_GST_APPSINK_CALLBACKS_
// set the callbacks
GstAppSinkCallbacks callbacks;
if (single_frame_) {
callbacks.new_preroll = callback_new_preroll;
callbacks.eos = NULL;
callbacks.new_sample = NULL;
Log::Info("Stream %s contains a single frame", std::to_string(id_).c_str());
}
else {
callbacks.new_preroll = callback_new_preroll;
callbacks.eos = callback_end_of_stream;
callbacks.new_sample = callback_new_sample;
}
gst_app_sink_set_callbacks (GST_APP_SINK(sink), &callbacks, this, NULL);
gst_app_sink_set_emit_signals (GST_APP_SINK(sink), false);
#else
// connect signals callbacks
g_signal_connect(G_OBJECT(sink), "new-preroll", G_CALLBACK (callback_new_preroll), this);
if (!single_frame_) {
g_signal_connect(G_OBJECT(sink), "new-sample", G_CALLBACK (callback_new_sample), this);
g_signal_connect(G_OBJECT(sink), "eos", G_CALLBACK (callback_end_of_stream), this);
}
gst_app_sink_set_emit_signals (GST_APP_SINK(sink), true);
#endif
// set to desired state (PLAY or PAUSE)
GstStateChangeReturn ret = gst_element_set_state (pipeline_, desired_state_);
if (ret == GST_STATE_CHANGE_FAILURE) {
Log::Warning("Stream %s Could not open '%s'", std::to_string(id_).c_str(), description_.c_str());
failed_ = true;
return;
}
else if (ret == GST_STATE_CHANGE_NO_PREROLL) {
Log::Info("Stream %s is a live stream", std::to_string(id_).c_str());
live_ = true;
}
// instruct the sink to send samples synched in time if not live source
gst_base_sink_set_sync (GST_BASE_SINK(sink), !live_);
// all good
Log::Info("Stream %s Opened '%s' (%d x %d)", std::to_string(id_).c_str(), description.c_str(), width_, height_);
ready_ = true;
// done with refs
gst_object_unref (sink);
gst_caps_unref (caps);
}
bool Stream::isOpen() const
{
return ready_;
}
bool Stream::failed() const
{
return failed_;
}
void Stream::close()
{
// not openned?
if (!ready_) {
// nothing else to change
return;
}
// un-ready
ready_ = false;
// clean up GST
if (pipeline_ != nullptr) {
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_NULL);
if (ret == GST_STATE_CHANGE_ASYNC) {
GstState state;
gst_element_get_state (pipeline_, &state, NULL, GST_CLOCK_TIME_NONE);
}
gst_object_unref (pipeline_);
pipeline_ = nullptr;
}
desired_state_ = GST_STATE_PAUSED;
// cleanup eventual remaining frame memory
for(guint i = 0; i < N_FRAME; i++){
if ( frame_[i].full ) {
gst_video_frame_unmap(&frame_[i].vframe);
frame_[i].status = INVALID;
}
}
write_index_ = 0;
last_index_ = 0;
// cleanup opengl texture
if (textureindex_)
glDeleteTextures(1, &textureindex_);
textureindex_ = 0;
// cleanup picture buffer
if (pbo_[0])
glDeleteBuffers(2, pbo_);
pbo_size_ = 0;
}
guint Stream::width() const
{
return width_;
}
guint Stream::height() const
{
return height_;
}
float Stream::aspectRatio() const
{
return static_cast<float>(width_) / static_cast<float>(height_);
}
void Stream::enable(bool on)
{
if ( !ready_ || pipeline_ == nullptr)
return;
if ( enabled_ != on ) {
enabled_ = on;
// default to pause
GstState requested_state = GST_STATE_PAUSED;
// unpause only if enabled
if (enabled_) {
requested_state = desired_state_;
}
// apply state change
GstStateChangeReturn ret = gst_element_set_state (pipeline_, requested_state);
if (ret == GST_STATE_CHANGE_FAILURE) {
Log::Warning("Stream %s Failed to enable", std::to_string(id_).c_str());
failed_ = true;
}
}
}
bool Stream::enabled() const
{
return enabled_;
}
bool Stream::singleFrame() const
{
return single_frame_;
}
bool Stream::live() const
{
return live_;
}
void Stream::play(bool on)
{
// ignore if disabled, and cannot play an image
if (!enabled_)
return;
// request state
GstState requested_state = on ? GST_STATE_PLAYING : GST_STATE_PAUSED;
// ignore if requesting twice same state
if (desired_state_ == requested_state)
return;
// accept request to the desired state
desired_state_ = requested_state;
// if not ready yet, the requested state will be handled later
if ( pipeline_ == nullptr )
return;
// all ready, apply state change immediately
GstStateChangeReturn ret = gst_element_set_state (pipeline_, desired_state_);
if (ret == GST_STATE_CHANGE_FAILURE) {
Log::Warning("Stream %s Failed to play", std::to_string(id_).c_str());
failed_ = true;
}
#ifdef STREAM_DEBUG
else if (on)
Log::Info("Stream %s Start", std::to_string(id_).c_str());
else
Log::Info("Stream %s Stop", std::to_string(id_).c_str());
#endif
// activate live-source
if (live_)
gst_element_get_state (pipeline_, NULL, NULL, GST_CLOCK_TIME_NONE);
// reset time counter
timecount_.reset();
}
bool Stream::isPlaying(bool testpipeline) const
{
// if not ready yet, answer with requested state
if ( !testpipeline || pipeline_ == nullptr || !enabled_)
return desired_state_ == GST_STATE_PLAYING;
// if ready, answer with actual state
GstState state;
gst_element_get_state (pipeline_, &state, NULL, GST_CLOCK_TIME_NONE);
return state == GST_STATE_PLAYING;
}
void Stream::init_texture(guint index)
{
glActiveTexture(GL_TEXTURE0);
glGenTextures(1, &textureindex_);
glBindTexture(GL_TEXTURE_2D, textureindex_);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width_, height_);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_,
GL_RGBA, GL_UNSIGNED_BYTE, frame_[index].vframe.data[0]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// set pbo image size
pbo_size_ = height_ * width_ * 4;
// create pixel buffer objects,
if (pbo_[0])
glDeleteBuffers(2, pbo_);
glGenBuffers(2, pbo_);
for(int i = 0; i < 2; i++ ) {
// create 2 PBOs
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo_[i]);
// glBufferDataARB with NULL pointer reserves only memory space.
glBufferData(GL_PIXEL_UNPACK_BUFFER, pbo_size_, 0, GL_STREAM_DRAW);
// fill in with reset picture
GLubyte* ptr = (GLubyte*) glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY);
if (ptr) {
// update data directly on the mapped buffer
memmove(ptr, frame_[index].vframe.data[0], pbo_size_);
// release pointer to mapping buffer
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
}
else {
// did not work, disable PBO
glDeleteBuffers(2, pbo_);
pbo_[0] = pbo_[1] = 0;
pbo_size_ = 0;
break;
}
}
// should be good to go, wrap it up
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
pbo_index_ = 0;
pbo_next_index_ = 1;
#ifdef STREAM_DEBUG
Log::Info("Stream %s Use Pixel Buffer Object texturing.", std::to_string(id_).c_str());
#endif
}
void Stream::fill_texture(guint index)
{
// is this the first frame ?
if (textureindex_ < 1)
{
// initialize texture
init_texture(index);
}
else {
glBindTexture(GL_TEXTURE_2D, textureindex_);
// use dual Pixel Buffer Object
if (pbo_size_ > 0) {
// In dual PBO mode, increment current index first then get the next index
pbo_index_ = (pbo_index_ + 1) % 2;
pbo_next_index_ = (pbo_index_ + 1) % 2;
// bind PBO to read pixels
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo_[pbo_index_]);
// copy pixels from PBO to texture object
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_, GL_RGBA, GL_UNSIGNED_BYTE, 0);
// bind the next PBO to write pixels
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo_[pbo_next_index_]);
// See http://www.songho.ca/opengl/gl_pbo.html#map for more details
glBufferData(GL_PIXEL_UNPACK_BUFFER, pbo_size_, 0, GL_STREAM_DRAW);
// map the buffer object into client's memory
GLubyte* ptr = (GLubyte*) glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY);
if (ptr) {
// update data directly on the mapped buffer
// NB : equivalent but faster (memmove instead of memcpy ?) than
// glNamedBufferSubData(pboIds[nextIndex], 0, imgsize, vp->getBuffer())
memmove(ptr, frame_[index].vframe.data[0], pbo_size_);
// release pointer to mapping buffer
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
}
// done with PBO
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
}
else {
// without PBO, use standard opengl (slower)
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_,
GL_RGBA, GL_UNSIGNED_BYTE, frame_[index].vframe.data[0]);
}
}
}
void Stream::update()
{
// discard
if (failed_)
return;
// not ready yet
if (!ready_)
return;
// // prevent unnecessary updates: disabled or already filled image
// if (!enabled_)
// return;
// local variables before trying to update
guint read_index = 0;
bool need_loop = false;
// locked access to current index
index_lock_.lock();
// get the last frame filled from fill_frame()
read_index = last_index_;
// Do NOT miss and jump directly to a pre-roll
for (guint i = 0; i < N_FRAME; ++i) {
if (frame_[i].status == PREROLL) {
read_index = i;
break;
}
}
// unlock access to index change
index_lock_.unlock();
// lock frame while reading it
frame_[read_index].access.lock();
// do not fill a frame twice
if (frame_[read_index].status != INVALID ) {
// is this an End-of-Stream frame ?
if (frame_[read_index].status == EOS )
{
// will execute seek command below (after unlock)
need_loop = true;
}
// otherwise just fill non-empty SAMPLE or PREROLL
else if (frame_[read_index].full)
{
// fill the texture with the frame at reading index
fill_texture(read_index);
// double update for pre-roll frame and dual PBO (ensure frame is displayed now)
if (frame_[read_index].status == PREROLL && pbo_size_ > 0)
fill_texture(read_index);
}
// avoid reading it again
frame_[read_index].status = INVALID;
}
// unkock frame after reading it
frame_[read_index].access.unlock();
if (need_loop) {
// stop on end of stream
play(false);
}
}
double Stream::updateFrameRate() const
{
return timecount_.frameRate();
}
// CALLBACKS
bool Stream::fill_frame(GstBuffer *buf, FrameStatus status)
{
// Log::Info("Stream fill frame");
// Do NOT overwrite an unread EOS
if ( frame_[write_index_].status == EOS )
write_index_ = (write_index_ + 1) % N_FRAME;
// lock access to frame
frame_[write_index_].access.lock();
// always empty frame before filling it again
if ( frame_[write_index_].full ) {
if ( GST_MINI_OBJECT_REFCOUNT_VALUE( &frame_[write_index_].vframe.buffer->mini_object ) > 0)
gst_video_frame_unmap(&frame_[write_index_].vframe);
frame_[write_index_].full = false;
}
// accept status of frame received
frame_[write_index_].status = status;
// a buffer is given (not EOS)
if (buf != NULL) {
// get the frame from buffer
if ( !gst_video_frame_map (&frame_[write_index_].vframe, &v_frame_video_info_, buf, GST_MAP_READ ) )
{
Log::Info("Stream %s Failed to map the video buffer", std::to_string(id_).c_str());
// free access to frame & exit
frame_[write_index_].status = INVALID;
frame_[write_index_].access.unlock();
return false;
}
// successfully filled the frame
frame_[write_index_].full = true;
// validate frame format
if( GST_VIDEO_INFO_IS_RGB(&(frame_[write_index_].vframe).info) && GST_VIDEO_INFO_N_PLANES(&(frame_[write_index_].vframe).info) == 1)
{
// set presentation time stamp
frame_[write_index_].position = buf->pts;
}
// full but invalid frame : will be deleted next iteration
// (should never happen)
else {
Log::Info("Stream %s Received an Invalid frame", std::to_string(id_).c_str());
frame_[write_index_].status = INVALID;
frame_[write_index_].access.unlock();
return false;
}
}
// else; null buffer for EOS: give a position
else {
frame_[write_index_].status = EOS;
#ifdef STREAM_DEBUG
Log::Info("Stream %s Reached End Of Stream", std::to_string(id_).c_str());
#endif
}
// unlock access to frame
frame_[write_index_].access.unlock();
// lock access to change current index (very quick)
index_lock_.lock();
// indicate update() that this is the last frame filled (and unlocked)
last_index_ = write_index_;
// unlock access to index change
index_lock_.unlock();
// for writing, we will access the next in stack
write_index_ = (write_index_ + 1) % N_FRAME;
// calculate actual FPS of update
timecount_.tic();
return true;
}
void Stream::callback_end_of_stream (GstAppSink *, gpointer p)
{
Stream *m = (Stream *)p;
if (m && m->ready_) {
m->fill_frame(NULL, Stream::EOS);
}
}
GstFlowReturn Stream::callback_new_preroll (GstAppSink *sink, gpointer p)
{
GstFlowReturn ret = GST_FLOW_OK;
// blocking read pre-roll samples
GstSample *sample = gst_app_sink_pull_preroll(sink);
// if got a valid sample
if (sample != NULL) {
// send frames to media player only if ready
Stream *m = (Stream *)p;
if (m && m->ready_) {
// get buffer from sample
GstBuffer *buf = gst_sample_get_buffer (sample);
// fill frame from buffer
if ( !m->fill_frame(buf, Stream::PREROLL) )
ret = GST_FLOW_ERROR;
}
}
else
ret = GST_FLOW_FLUSHING;
// release sample
gst_sample_unref (sample);
return ret;
}
GstFlowReturn Stream::callback_new_sample (GstAppSink *sink, gpointer p)
{
GstFlowReturn ret = GST_FLOW_OK;
// if (gst_app_sink_is_eos (sink))
// Log::Info("callback_new_sample got EOS");
// non-blocking read new sample
GstSample *sample = gst_app_sink_pull_sample(sink);
// if got a valid sample
if (sample != NULL && !gst_app_sink_is_eos (sink)) {
// send frames to media player only if ready
Stream *m = (Stream *)p;
if (m && m->ready_) {
// get buffer from sample (valid until sample is released)
GstBuffer *buf = gst_sample_get_buffer (sample) ;
// fill frame with buffer
if ( !m->fill_frame(buf, Stream::SAMPLE) )
ret = GST_FLOW_ERROR;
}
}
else
ret = GST_FLOW_FLUSHING;
// release sample
gst_sample_unref (sample);
return ret;
}
Stream::TimeCounter::TimeCounter() {
reset();
}
void Stream::TimeCounter::tic ()
{
// how long since last time
GstClockTime t = gst_util_get_timestamp ();
GstClockTime dt = t - last_time;
// one more frame since last time
nbFrames++;
// calculate instantaneous framerate
// Exponential moving averate with previous framerate to filter jitter (50/50)
// The divition of frame/time is done on long integer GstClockTime, counting in microsecond
// NB: factor 100 to get 0.01 precision
fps = 0.5 * fps + 0.005 * static_cast<double>( ( 100 * GST_SECOND * nbFrames ) / dt );
// reset counter every second
if ( dt >= GST_SECOND)
{
last_time = t;
nbFrames = 0;
}
}
GstClockTime Stream::TimeCounter::dt ()
{
GstClockTime t = gst_util_get_timestamp ();
GstClockTime dt = t - tic_time;
tic_time = t;
// return the instantaneous delta t
return dt;
}
void Stream::TimeCounter::reset ()
{
last_time = gst_util_get_timestamp ();;
tic_time = last_time;
nbFrames = 0;
fps = 0.0;
}
double Stream::TimeCounter::frameRate() const
{
return fps;
}

200
Stream.h Normal file
View File

@@ -0,0 +1,200 @@
#ifndef STREAM_H
#define STREAM_H
#include <string>
#include <atomic>
#include <mutex>
#include <future>
// GStreamer
#include <gst/pbutils/pbutils.h>
#include <gst/app/gstappsink.h>
// Forward declare classes referenced
class Visitor;
#define N_FRAME 3
class Stream {
public:
/**
* Constructor of a GStreamer Stream
*/
Stream();
/**
* Destructor.
*/
~Stream();
/**
* Get unique id
*/
inline uint64_t id() const { return id_; }
/**
* Open a media using gstreamer pipeline keyword
* */
void open(const std::string &gstreamer_description, int w = 1024, int h = 576);
/**
* Get description string
* */
std::string description() const;
/**
* True if a media was oppenned
* */
bool isOpen() const;
/**
* True if problem occured
* */
bool failed() const;
/**
* Close the Media
* */
void close();
/**
* Update texture with latest frame
* Must be called in rendering update loop
* */
virtual void update();
/**
* Enable / Disable
* Suspend playing activity
* (restores playing state when re-enabled)
* */
void enable(bool on);
/**
* True if enabled
* */
bool enabled() const;
/**
* True if its an image
* */
bool singleFrame() const;
/**
* True if its a live stream
* */
bool live() const;
/**
* Pause / Play
* Can play backward if play speed is negative
* */
void play(bool on);
/**
* Get Pause / Play status
* Performs a full check of the Gstreamer pipeline if testpipeline is true
* */
bool isPlaying(bool testpipeline = false) const;
/**
* Get rendering update framerate
* measured during play
* */
double updateFrameRate() const;
/**
* Get frame width
* */
guint width() const;
/**
* Get frame height
* */
guint height() const;
/**
* Get frames displayt aspect ratio
* NB: can be different than width() / height()
* */
float aspectRatio() const;
/**
* Get the OpenGL texture
* Must be called in OpenGL context
* */
guint texture() const;
/**
* Accept visitors
* Used for saving session file
* */
void accept(Visitor& v);
protected:
// video player description
uint64_t id_;
std::string description_;
guint textureindex_;
// general properties of media
guint width_;
guint height_;
bool single_frame_;
bool live_;
// GST & Play status
GstState desired_state_;
GstElement *pipeline_;
GstVideoInfo v_frame_video_info_;
std::atomic<bool> ready_;
std::atomic<bool> failed_;
bool enabled_;
// fps counter
struct TimeCounter {
GstClockTime last_time;
GstClockTime tic_time;
int nbFrames;
gdouble fps;
public:
TimeCounter();
GstClockTime dt();
void tic();
void reset();
gdouble frameRate() const;
};
TimeCounter timecount_;
// frame stack
typedef enum {
SAMPLE = 0,
PREROLL = 1,
EOS = 2,
INVALID = 3
} FrameStatus;
struct Frame {
GstVideoFrame vframe;
FrameStatus status;
bool full;
GstClockTime position;
std::mutex access;
Frame() {
full = false;
status = INVALID;
position = GST_CLOCK_TIME_NONE;
}
};
Frame frame_[N_FRAME];
guint write_index_;
guint last_index_;
std::mutex index_lock_;
// for PBO
guint pbo_[2];
guint pbo_index_, pbo_next_index_;
guint pbo_size_;
// gst pipeline control
virtual void execute_open();
// gst frame filling
void init_texture(guint index);
void fill_texture(guint index);
bool fill_frame(GstBuffer *buf, FrameStatus status);
// gst callbacks
static void callback_end_of_stream (GstAppSink *, gpointer);
static GstFlowReturn callback_new_preroll (GstAppSink *, gpointer );
static GstFlowReturn callback_new_sample (GstAppSink *, gpointer);
};
#endif // STREAM_H

117
StreamSource.cpp Normal file
View File

@@ -0,0 +1,117 @@
#include <sstream>
#include <glm/gtc/matrix_transform.hpp>
#include "StreamSource.h"
#include "defines.h"
#include "ImageShader.h"
#include "Resource.h"
#include "Decorations.h"
#include "Stream.h"
#include "Visitor.h"
#include "Log.h"
GenericStreamSource::GenericStreamSource() : StreamSource()
{
// create stream
stream_ = new Stream;
// set symbol
symbol_ = new Symbol(Symbol::EMPTY, glm::vec3(0.8f, 0.8f, 0.01f));
}
void GenericStreamSource::setDescription(const std::string &desc)
{
Log::Notify("Creating Source with Stream description '%s'", desc.c_str());
stream_->open(desc);
stream_->play(true);
}
void GenericStreamSource::accept(Visitor& v)
{
Source::accept(v);
if (!failed())
v.visit(*this);
}
StreamSource::StreamSource() : Source(), stream_(nullptr)
{
}
StreamSource::~StreamSource()
{
// delete stream
if (stream_)
delete stream_;
}
bool StreamSource::failed() const
{
return (stream_ != nullptr && stream_->failed() );
}
uint StreamSource::texture() const
{
if (stream_ == nullptr)
return Resource::getTextureBlack();
else
return stream_->texture();
}
void StreamSource::init()
{
if ( stream_ && stream_->isOpen() ) {
// update video
stream_->update();
// once the texture of media player is created
if (stream_->texture() != Resource::getTextureBlack()) {
// get the texture index from media player, apply it to the media surface
texturesurface_->setTextureIndex( stream_->texture() );
// create Frame buffer matching size of media player
float height = float(stream_->width()) / stream_->aspectRatio();
FrameBuffer *renderbuffer = new FrameBuffer(stream_->width(), (uint)height, true);
// set the renderbuffer of the source and attach rendering nodes
attach(renderbuffer);
// deep update to reorder
View::need_deep_update_++;
// force update of activation mode
active_ = true;
// done init
initialized_ = true;
Log::Info("Source '%s' linked to Stream %s", name().c_str(), std::to_string(stream_->id()).c_str());
}
}
}
void StreamSource::setActive (bool on)
{
bool was_active = active_;
Source::setActive(on);
// change status of media player (only if status changed)
if ( active_ != was_active ) {
if (stream_)
stream_->enable(active_);
}
}
void StreamSource::update(float dt)
{
Source::update(dt);
// update stream
if (stream_)
stream_->update();
}

70
StreamSource.h Normal file
View File

@@ -0,0 +1,70 @@
#ifndef STREAMSOURCE_H
#define STREAMSOURCE_H
#include "Stream.h"
#include "Source.h"
/**
* @brief The StreamSource class
*
* StreamSource is a virtual base class
* (because stream() = 0)
* based on the virtual base class Source
* that implements the update and display
* of a Stream object (gstreamer generic)
*
* StreamSource does *not* create a stream
* in its constructor to let this for the
* specific implementation of the subclass.
* Therefore it cannot be instanciated and
* it cannot give access to its stream.
*
*/
class StreamSource: public Source
{
public:
StreamSource();
virtual ~StreamSource();
// implementation of source API
void update (float dt) override;
void setActive (bool on) override;
bool failed() const override;
uint texture() const override;
// pure virtual interface
virtual Stream *stream() const = 0;
protected:
void init() override;
Stream *stream_;
};
/**
* @brief The GenericStreamSource class
*
* Implements the StreamSource
* with an initialization
* using a generic description
* of the gstreamer pipeline.
*
* It can be instanciated.
*/
class GenericStreamSource : public StreamSource
{
public:
GenericStreamSource();
// Source interface
void accept (Visitor& v) override;
// StreamSource interface
Stream *stream() const override { return stream_; }
// specific interface
void setDescription(const std::string &desc);
};
#endif // STREAMSOURCE_H

404
Streamer.cpp Normal file
View File

@@ -0,0 +1,404 @@
#include <thread>
#include <sstream>
// Desktop OpenGL function loader
#include <glad/glad.h>
// standalone image loader
#include <stb_image.h>
#include <stb_image_write.h>
// gstreamer
#include <gst/gstformat.h>
#include <gst/video/video.h>
//osc
#include "osc/OscOutboundPacketStream.h"
#include "Settings.h"
#include "GstToolkit.h"
#include "defines.h"
#include "SystemToolkit.h"
#include "Session.h"
#include "FrameBuffer.h"
#include "Log.h"
#include "Connection.h"
#include "NetworkToolkit.h"
#include "Streamer.h"
#include <iostream>
#include <cstring>
#ifndef NDEBUG
#define STREAMER_DEBUG
#endif
void StreamingRequestListener::ProcessMessage( const osc::ReceivedMessage& m,
const IpEndpointName& remoteEndpoint )
{
char sender[IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH];
remoteEndpoint.AddressAndPortAsString(sender);
try{
if( std::strcmp( m.AddressPattern(), OSC_PREFIX OSC_STREAM_REQUEST) == 0 ){
#ifdef STREAMER_DEBUG
Log::Info("%s wants a stream.", sender);
#endif
osc::ReceivedMessage::const_iterator arg = m.ArgumentsBegin();
int reply_to_port = (arg++)->AsInt32();
const char *client_name = (arg++)->AsString();
if (Streaming::manager().enabled())
Streaming::manager().addStream(sender, reply_to_port, client_name);
else
Streaming::manager().refuseStream(sender, reply_to_port);
}
else if( std::strcmp( m.AddressPattern(), OSC_PREFIX OSC_STREAM_DISCONNECT) == 0 ){
#ifdef STREAMER_DEBUG
Log::Info("%s does not need streaming anymore.", sender);
#endif
osc::ReceivedMessage::const_iterator arg = m.ArgumentsBegin();
int port = (arg++)->AsInt32();
Streaming::manager().removeStream(sender, port);
}
}
catch( osc::Exception& e ){
// any parsing errors such as unexpected argument types, or
// missing arguments get thrown as exceptions.
Log::Info("error while parsing message '%s' from %s : %s", m.AddressPattern(), sender, e.what());
}
}
void wait_for_request_(UdpListeningReceiveSocket *receiver)
{
receiver->Run();
}
Streaming::Streaming() : enabled_(false)
{
int port = Connection::manager().info().port_stream_request;
receiver_ = new UdpListeningReceiveSocket(IpEndpointName( IpEndpointName::ANY_ADDRESS, port ), &listener_ );
std::thread(wait_for_request_, receiver_).detach();
}
Streaming::~Streaming()
{
if (receiver_!=nullptr) {
receiver_->Break();
delete receiver_;
}
}
bool Streaming::busy()
{
bool b = false;
streamers_lock_.lock();
std::vector<VideoStreamer *>::const_iterator sit = streamers_.begin();
for (; sit != streamers_.end() && !b; sit++)
b = (*sit)->busy() ;
streamers_lock_.unlock();
return b;
}
std::vector<std::string> Streaming::listStreams()
{
std::vector<std::string> ls;
streamers_lock_.lock();
std::vector<VideoStreamer *>::const_iterator sit = streamers_.begin();
for (; sit != streamers_.end(); sit++)
ls.push_back( (*sit)->info() );
streamers_lock_.unlock();
return ls;
}
void Streaming::enable(bool on)
{
if (on) {
// accept streaming requests
enabled_ = true;
Log::Info("Accepting stream requests to %s.", Connection::manager().info().name.c_str());
}
else {
// refuse streaming requests
enabled_ = false;
// ending and removing all streaming
streamers_lock_.lock();
for (auto sit = streamers_.begin(); sit != streamers_.end(); sit=streamers_.erase(sit))
(*sit)->stop();
streamers_lock_.unlock();
Log::Info("Refusing stream requests to %s. No streaming ongoing.", Connection::manager().info().name.c_str());
}
}
void Streaming::removeStream(const std::string &sender, int port)
{
// get ip of sender
std::string sender_ip = sender.substr(0, sender.find_last_of(":"));
// parse the list for a streamers matching IP and port
streamers_lock_.lock();
std::vector<VideoStreamer *>::const_iterator sit = streamers_.begin();
for (; sit != streamers_.end(); sit++){
NetworkToolkit::StreamConfig config = (*sit)->config_;
if (config.client_address.compare(sender_ip) == 0 && config.port == port ) {
#ifdef STREAMER_DEBUG
Log::Info("Ending streaming to %s:%d", config.client_address.c_str(), config.port);
#endif
// match: stop this streamer
(*sit)->stop();
// remove from list
streamers_.erase(sit);
break;
}
}
streamers_lock_.unlock();
}
void Streaming::removeStreams(const std::string &clientname)
{
// remove all streamers matching given IP
streamers_lock_.lock();
std::vector<VideoStreamer *>::const_iterator sit = streamers_.begin();
while ( sit != streamers_.end() ){
NetworkToolkit::StreamConfig config = (*sit)->config_;
if (config.client_name.compare(clientname) == 0) {
#ifdef STREAMER_DEBUG
Log::Info("Ending streaming to %s:%d", config.client_address.c_str(), config.port);
#endif
// match: stop this streamer
(*sit)->stop();
// remove from list
sit = streamers_.erase(sit);
}
else
sit++;
}
streamers_lock_.unlock();
}
void Streaming::refuseStream(const std::string &sender, int reply_to)
{
// get ip of client
std::string sender_ip = sender.substr(0, sender.find_last_of(":"));
// prepare to reply to client
IpEndpointName host( sender_ip.c_str(), reply_to );
UdpTransmitSocket socket( host );
// build OSC message
char buffer[IP_MTU_SIZE];
osc::OutboundPacketStream p( buffer, IP_MTU_SIZE );
p.Clear();
p << osc::BeginMessage( OSC_PREFIX OSC_STREAM_REJECT );
p << osc::EndMessage;
// send OSC message to client
socket.Send( p.Data(), p.Size() );
// inform user
Log::Warning("A connection request for streaming came and was rejected.\nYou can Accept connections from the Output window.");
}
void Streaming::addStream(const std::string &sender, int reply_to, const std::string &clientname)
{
// get ip of client
std::string sender_ip = sender.substr(0, sender.find_last_of(":"));
// get port used to send the request
std::string sender_port = sender.substr(sender.find_last_of(":") + 1);
// prepare to reply to client
IpEndpointName host( sender_ip.c_str(), reply_to );
UdpTransmitSocket socket( host );
// prepare an offer
NetworkToolkit::StreamConfig conf;
conf.client_address = sender_ip;
conf.client_name = clientname;
conf.port = std::stoi(sender_port); // this port seems free, so re-use it!
conf.width = FrameGrabbing::manager().width();
conf.height = FrameGrabbing::manager().height();
// TEMP DISABLED : TODO Fix snap to allow system wide shared access
// offer SHM if same IP that our host IP (i.e. on the same machine)
// if( NetworkToolkit::is_host_ip(conf.client_address) )
// conf.protocol = NetworkToolkit::SHM_RAW;
// // any other IP : offer network streaming
// else
conf.protocol = NetworkToolkit::UDP_JPEG;
// build OSC message
char buffer[IP_MTU_SIZE];
osc::OutboundPacketStream p( buffer, IP_MTU_SIZE );
p.Clear();
p << osc::BeginMessage( OSC_PREFIX OSC_STREAM_OFFER );
p << conf.port;
p << (int) conf.protocol;
p << conf.width << conf.height;
p << osc::EndMessage;
// send OSC message to client
socket.Send( p.Data(), p.Size() );
#ifdef STREAMER_DEBUG
Log::Info("Replying to %s:%d", sender_ip.c_str(), reply_to);
Log::Info("Starting streaming to %s:%d", sender_ip.c_str(), conf.port);
#endif
// create streamer & remember it
VideoStreamer *streamer = new VideoStreamer(conf);
streamers_lock_.lock();
streamers_.push_back(streamer);
streamers_lock_.unlock();
// start streamer
FrameGrabbing::manager().add(streamer);
}
VideoStreamer::VideoStreamer(NetworkToolkit::StreamConfig conf): FrameGrabber(), config_(conf)
{
}
void VideoStreamer::init(GstCaps *caps)
{
// ignore
if (caps == nullptr)
return;
// check that config matches the given buffer properties
gint w = 0, h = 0;
GstStructure *capstruct = gst_caps_get_structure (caps, 0);
if ( gst_structure_has_field (capstruct, "width"))
gst_structure_get_int (capstruct, "width", &w);
if ( gst_structure_has_field (capstruct, "height"))
gst_structure_get_int (capstruct, "height", &h);
if ( config_.width != w || config_.height != h) {
Log::Warning("Streaming cannot start: given frames (%d x %d) incompatible with stream (%d x %d)",
w, w, config_.width, config_.height);
finished_ = true;
return;
}
// prevent eroneous protocol values
if (config_.protocol < 0 || config_.protocol >= NetworkToolkit::DEFAULT)
config_.protocol = NetworkToolkit::UDP_JPEG;
// create a gstreamer pipeline
std::string description = "appsrc name=src ! videoconvert ! ";
description += NetworkToolkit::protocol_send_pipeline[config_.protocol];
// parse pipeline descriptor
GError *error = NULL;
pipeline_ = gst_parse_launch (description.c_str(), &error);
if (error != NULL) {
Log::Warning("VideoStreamer Could not construct pipeline %s:\n%s", description.c_str(), error->message);
g_clear_error (&error);
finished_ = true;
return;
}
// setup streaming sink
if (config_.protocol == NetworkToolkit::UDP_JPEG || config_.protocol == NetworkToolkit::UDP_H264) {
g_object_set (G_OBJECT (gst_bin_get_by_name (GST_BIN (pipeline_), "sink")),
"host", config_.client_address.c_str(),
"port", config_.port, NULL);
}
else if (config_.protocol == NetworkToolkit::SHM_RAW) {
std::string path = SystemToolkit::full_filename(SystemToolkit::temp_path(), "shm");
path += std::to_string(config_.port);
g_object_set (G_OBJECT (gst_bin_get_by_name (GST_BIN (pipeline_), "sink")),
"socket-path", path.c_str(), NULL);
}
// setup custom app source
src_ = GST_APP_SRC( gst_bin_get_by_name (GST_BIN (pipeline_), "src") );
if (src_) {
g_object_set (G_OBJECT (src_),
"stream-type", GST_APP_STREAM_TYPE_STREAM,
"is-live", TRUE,
"format", GST_FORMAT_TIME,
// "do-timestamp", TRUE,
NULL);
// Direct encoding (no buffering)
gst_app_src_set_max_bytes( src_, 0 );
// instruct src to use the required caps
caps_ = gst_caps_copy( caps );
gst_app_src_set_caps (src_, caps_);
// setup callbacks
GstAppSrcCallbacks callbacks;
callbacks.need_data = FrameGrabber::callback_need_data;
callbacks.enough_data = FrameGrabber::callback_enough_data;
callbacks.seek_data = NULL; // stream type is not seekable
gst_app_src_set_callbacks (src_, &callbacks, this, NULL);
}
else {
Log::Warning("VideoStreamer Could not configure capture source");
finished_ = true;
return;
}
// start recording
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_PLAYING);
if (ret == GST_STATE_CHANGE_FAILURE) {
Log::Warning("VideoStreamer failed");
finished_ = true;
return;
}
// all good
Log::Notify("Streaming to %s.", config_.client_name.c_str());
// start streaming !!
active_ = true;
}
void VideoStreamer::terminate()
{
// send EOS
gst_app_src_end_of_stream (src_);
// make sure the shared memory socket is deleted
if (config_.protocol == NetworkToolkit::SHM_RAW) {
std::string path = SystemToolkit::full_filename(SystemToolkit::temp_path(), "shm");
path += std::to_string(config_.port);
SystemToolkit::remove_file(path);
}
Log::Notify("Streaming to %s finished after %s s.", config_.client_name.c_str(),
GstToolkit::time_to_string(timestamp_).c_str());
}
void VideoStreamer::stop ()
{
// stop recording
FrameGrabber::stop ();
// force finished
finished_ = true;
}
std::string VideoStreamer::info() const
{
std::ostringstream ret;
if (active_) {
ret << NetworkToolkit::protocol_name[config_.protocol];
ret << " to ";
ret << config_.client_name;
}
else
ret << "Streaming terminated.";
return ret.str();
}

85
Streamer.h Normal file
View File

@@ -0,0 +1,85 @@
#ifndef STREAMER_H
#define STREAMER_H
#include <mutex>
#include <gst/pbutils/pbutils.h>
#include <gst/app/gstappsrc.h>
#include "osc/OscReceivedElements.h"
#include "osc/OscPacketListener.h"
#include "ip/UdpSocket.h"
#include "NetworkToolkit.h"
#include "FrameGrabber.h"
class Session;
class VideoStreamer;
class StreamingRequestListener : public osc::OscPacketListener {
protected:
virtual void ProcessMessage( const osc::ReceivedMessage& m,
const IpEndpointName& remoteEndpoint );
};
class Streaming
{
friend class StreamingRequestListener;
// Private Constructor
Streaming();
Streaming(Streaming const& copy); // Not Implemented
Streaming& operator=(Streaming const& copy); // Not Implemented
public:
static Streaming& manager()
{
// The only instance
static Streaming _instance;
return _instance;
}
~Streaming();
void enable(bool on);
inline bool enabled() const { return enabled_; }
void removeStreams(const std::string &clientname);
void removeStream(const std::string &sender, int port);
bool busy();
std::vector<std::string> listStreams();
protected:
void addStream(const std::string &sender, int reply_to, const std::string &clientname);
void refuseStream(const std::string &sender, int reply_to);
private:
bool enabled_;
StreamingRequestListener listener_;
UdpListeningReceiveSocket *receiver_;
std::vector<VideoStreamer *> streamers_;
std::mutex streamers_lock_;
};
class VideoStreamer : public FrameGrabber
{
friend class Streaming;
void init(GstCaps *caps) override;
void terminate() override;
void stop() override;
// connection information
NetworkToolkit::StreamConfig config_;
public:
VideoStreamer(NetworkToolkit::StreamConfig conf);
std::string info() const override;
};
#endif // STREAMER_H

View File

@@ -7,6 +7,10 @@
#include <ctime>
#include <chrono>
#include <locale>
#include <unicode/ustream.h>
#include <unicode/translit.h>
using namespace std;
#ifdef WIN32
@@ -214,6 +218,17 @@ bool SystemToolkit::create_directory(const string& path)
// TODO : verify WIN32 implementation
}
bool SystemToolkit::remove_file(const string& path)
{
bool ret = true;
if (file_exists(path)) {
ret = (remove(path.c_str()) == 0);
}
return ret;
// TODO : verify WIN32 implementation
}
string SystemToolkit::settings_path()
{
// start from home folder
@@ -243,6 +258,21 @@ string SystemToolkit::settings_path()
}
}
string SystemToolkit::temp_path()
{
string temp;
const char *tmpdir = getenv("TMPDIR");
if (tmpdir)
temp = std::string(tmpdir);
else
temp = std::string( P_tmpdir );
temp += PATH_SEP;
return temp;
// TODO : verify WIN32 implementation
}
string SystemToolkit::full_filename(const std::string& path, const string &filename)
{
string fullfilename = path;
@@ -314,5 +344,38 @@ void SystemToolkit::open(const string& url)
#endif
}
void SystemToolkit::execute(const string& command)
{
#ifdef WIN32
ShellExecuteA( nullptr, nullptr, url.c_str(), nullptr, nullptr, 0 );
#elif defined APPLE
int r = system( command.c_str() );
#else
int r = system( command.c_str() );
#endif
}
// example :
// std::thread (SystemToolkit::execute,
// "gst-launch-1.0 udpsrc port=5000 ! application/x-rtp,encoding-name=JPEG,payload=26 ! rtpjpegdepay ! jpegdec ! autovideosink").detach();;
// Using ICU transliteration :
// https://unicode-org.github.io/icu/userguide/transforms/general/#icu-transliterators
std::string SystemToolkit::transliterate(std::string input)
{
auto ucs = icu::UnicodeString::fromUTF8(input);
UErrorCode status = U_ZERO_ERROR;
icu::Transliterator *firstTrans = icu::Transliterator::createInstance(
"any-NFKD ; [:Nonspacing Mark:] Remove; NFKC; Latin", UTRANS_FORWARD, status);
firstTrans->transliterate(ucs);
icu::Transliterator *secondTrans = icu::Transliterator::createInstance(
"any-NFKD ; [:Nonspacing Mark:] Remove; [@!#$*%~] Remove; NFKC", UTRANS_FORWARD, status);
secondTrans->transliterate(ucs);
std::ostringstream output;
output << ucs;
return output.str();
}

View File

@@ -27,6 +27,9 @@ namespace SystemToolkit
// get the OS dependent path where to store settings
std::string settings_path();
// get the OS dependent path where to store temporary files
std::string temp_path();
// builds the OS dependent complete file name
std::string full_filename(const std::string& path, const std::string& filename);
@@ -54,18 +57,27 @@ namespace SystemToolkit
// true of file exists
bool file_exists(const std::string& path);
// create directory and return true on success
bool create_directory(const std::string& path);
// remove file and return true if the file does not exist after this call
bool remove_file(const std::string& path);
// try to open the file with system
void open(const std::string& path);
// try to execute a command
void execute(const std::string& command);
// return memory resident set size used (in bytes)
long memory_usage();
long memory_max_usage();
// get a string to display memory size with unit KB, MB, GB, TB
std::string byte_to_string(long b);
// get a transliteration to Latin of any string
std::string transliterate(std::string input);
}
#endif // SYSTEMTOOLKIT_H

View File

@@ -34,6 +34,7 @@ Timeline& Timeline::operator = (const Timeline& b)
this->timing_ = b.timing_;
this->step_ = b.step_;
this->gaps_ = b.gaps_;
this->gaps_array_need_update_ = b.gaps_array_need_update_;
memcpy( this->gapsArray_, b.gapsArray_, MAX_TIMELINE_ARRAY * sizeof(float));
memcpy( this->fadingArray_, b.fadingArray_, MAX_TIMELINE_ARRAY * sizeof(float));
}
@@ -72,6 +73,13 @@ void Timeline::setStep(GstClockTime dt)
step_ = dt;
}
void Timeline::setTiming(TimeInterval interval, GstClockTime step)
{
timing_ = interval;
if (step != GST_CLOCK_TIME_NONE)
step_ = step;
}
GstClockTime Timeline::next(GstClockTime time) const
{
GstClockTime next_time = time;

View File

@@ -88,9 +88,10 @@ public:
// global properties of the timeline
// timeline is valid only if all 3 are set
void setFirst(GstClockTime first);
void setFirst(GstClockTime first); // TODO : do we really use FIRST ?
void setEnd(GstClockTime end);
void setStep(GstClockTime dt);
void setTiming(TimeInterval interval, GstClockTime step = GST_CLOCK_TIME_NONE);
// Timing manipulation
inline GstClockTime begin() const { return timing_.begin; }
@@ -100,6 +101,7 @@ public:
inline GstClockTime step() const { return step_; }
inline GstClockTime duration() const { return timing_.duration(); }
inline size_t numFrames() const { return duration() / step_; }
inline TimeInterval interval() const { return timing_; }
GstClockTime next(GstClockTime time) const;
GstClockTime previous(GstClockTime time) const;

View File

@@ -70,8 +70,8 @@ void RotateToCallback::update(Node *n, float dt)
}
}
BounceScaleCallback::BounceScaleCallback(float duration) : UpdateCallback(),
duration_(duration), progress_(0.f), initialized_(false)
BounceScaleCallback::BounceScaleCallback(float scale) : UpdateCallback(),
duration_(100.f), progress_(0.f), initialized_(false), scale_(scale)
{
}
@@ -87,8 +87,8 @@ void BounceScaleCallback::update(Node *n, float dt)
// calculate amplitude of movement
progress_ += dt / duration_;
n->scale_.x = initial_scale_.x + (initial_scale_.x * 0.05f) * sin(M_PI * progress_);
n->scale_.y = initial_scale_.y + (initial_scale_.y * 0.05f) * sin(M_PI * progress_);
n->scale_.x = initial_scale_.x + (initial_scale_.x * scale_) * sin(M_PI * progress_);
n->scale_.y = initial_scale_.y + (initial_scale_.y * scale_) * sin(M_PI * progress_);
// end of movement
if ( progress_ > 1.f) {

View File

@@ -55,12 +55,13 @@ public:
class BounceScaleCallback : public UpdateCallback
{
float duration_;
float scale_;
float progress_;
bool initialized_;
glm::vec3 initial_scale_;
public:
BounceScaleCallback(float duration = 100.f);
BounceScaleCallback(float scale = 0.05f);
void update(Node *n, float dt);
};

File diff suppressed because it is too large Load Diff

View File

@@ -26,7 +26,8 @@ public:
Source *getSource();
void Render(float width, bool controlbutton = false);
inline bool ready() const { return source_ != nullptr; }
bool ready() const;
inline bool filled() const { return source_ != nullptr; }
};
class Navigator
@@ -40,7 +41,9 @@ class Navigator
// behavior pannel
bool pannel_visible_;
bool view_pannel_visible;
bool selected_button[NAV_COUNT];
int pattern_type;
void clearButtonSelection();
void applyButtonSelection(int index);
@@ -49,8 +52,7 @@ class Navigator
void RenderMainPannel();
void RenderTransitionPannel();
void RenderNewPannel();
int new_source_type_;
char file_browser_path_[2048];
void RenderViewPannel(ImVec2 draw_pos, ImVec2 draw_size);
SourcePreview new_source_preview_;
@@ -107,21 +109,15 @@ class UserInterface
bool ctrl_modifier_active;
bool alt_modifier_active;
bool shift_modifier_active;
bool show_vimix_config;
bool show_vimix_about;
bool show_imgui_about;
bool show_gst_about;
bool show_opengl_about;
unsigned int screenshot_step;
// typedef enum {
// FILE_DIALOG_INACTIVE = 0,
// FILE_DIALOG_ACTIVE,
// FILE_DIALOG_FINISHED
// } FileDialogStatus;
// FileDialogStatus filestatus_;
// std::string filename_;
// void startOpenFileDialog();
// frame grabbers
uint64_t video_recorder_;
uint64_t webcam_emulator_;
// Private Constructor
UserInterface();
@@ -167,10 +163,12 @@ protected:
void selectOpenFilename();
void RenderPreview();
void RenderHistory();
void RenderShaderEditor();
void handleKeyboard();
void handleMouse();
void handleScreenshot();
void RenderAbout(bool* p_open);
};
#endif /* #define __UI_MANAGER_H_ */

1915
View.cpp

File diff suppressed because it is too large Load Diff

125
View.h
View File

@@ -5,15 +5,21 @@
#include "Scene.h"
#include "FrameBuffer.h"
class Source;
typedef std::list<Source *> SourceList;
class SessionSource;
class Surface;
class Symbol;
class Mesh;
class Frame;
class View
{
public:
typedef enum {RENDERING = 0, MIXING=1, GEOMETRY=2, LAYER=3, TRANSITION=4, INVALID=5 } Mode;
typedef enum {RENDERING = 0, MIXING=1, GEOMETRY=2, LAYER=3, APPEARANCE=4, TRANSITION=5, INVALID=6 } Mode;
View(Mode m);
virtual ~View() {}
@@ -23,7 +29,9 @@ public:
virtual void update (float dt);
virtual void draw ();
virtual void zoom (float) {}
virtual void zoom (float);
virtual void resize (int) {}
virtual int size () { return 50; }
virtual void recenter();
virtual void centerSource(Source *) {}
@@ -58,27 +66,32 @@ public:
// grab a source provided a start and an end point in screen coordinates and the picking point
virtual void initiate();
virtual void terminate() {}
virtual void terminate();
virtual Cursor grab (Source*, glm::vec2, glm::vec2, std::pair<Node *, glm::vec2>) {
return Cursor();
}
// // test mouse over provided a point in screen coordinates and the picking point
// virtual Cursor over (Source*, glm::vec2, std::pair<Node *, glm::vec2>) {
// return Cursor();
// }
// test mouse over provided a point in screen coordinates
virtual Cursor over (glm::vec2) {
return Cursor();
}
// left-right [-1 1] and up-down [1 -1] action from arrow keys
virtual void arrow (glm::vec2) {}
// accessible scene
Scene scene;
// reordering scene when necessary
static bool need_deep_update_;
static uint need_deep_update_;
protected:
virtual void restoreSettings();
virtual void saveSettings();
std::string current_action_;
uint64_t current_id_;
Mode mode_;
};
@@ -90,13 +103,15 @@ public:
void draw () override;
void update (float dt) override;
void zoom (float factor) override;
void resize (int) override;
int size () override;
void centerSource(Source *) override;
void select(glm::vec2, glm::vec2) override;
void selectAll() override;
std::pair<Node *, glm::vec2> pick(glm::vec2) override;
Cursor grab (Source *s, glm::vec2 from, glm::vec2 to, std::pair<Node *, glm::vec2>) override;
Cursor drag (glm::vec2, glm::vec2) override;
void arrow (glm::vec2) override;
void setAlpha (Source *s);
inline float limboScale() { return limbo_scale_; }
@@ -109,7 +124,9 @@ private:
class Disk *slider_;
class Disk *button_white_;
class Disk *button_black_;
class Disk *stashCircle_;
class Mesh *mixingCircle_;
};
class RenderView : public View
@@ -139,15 +156,16 @@ public:
void draw () override;
void update (float dt) override;
void zoom (float factor) override;
void resize (int) override;
int size () override;
std::pair<Node *, glm::vec2> pick(glm::vec2 P) override;
Cursor grab (Source *s, glm::vec2 from, glm::vec2 to, std::pair<Node *, glm::vec2> pick) override;
// Cursor over (Source *s, glm::vec2 pos, std::pair<Node *, glm::vec2> pick) override;
Cursor drag (glm::vec2, glm::vec2) override;
void terminate() override;
void arrow (glm::vec2) override;
private:
Surface *output_surface_;
Node *overlay_position_;
Node *overlay_position_cross_;
Node *overlay_rotation_;
@@ -157,6 +175,8 @@ private:
Node *overlay_scaling_;
Node *overlay_scaling_cross_;
Node *overlay_scaling_grid_;
Node *overlay_crop_;
bool show_context_menu_;
};
class LayerView : public View
@@ -165,15 +185,19 @@ public:
LayerView();
void update (float dt) override;
void zoom (float factor) override;
void resize (int) override;
int size () override;
Cursor grab (Source *s, glm::vec2 from, glm::vec2 to, std::pair<Node *, glm::vec2> pick) override;
Cursor drag (glm::vec2, glm::vec2) override;
void arrow (glm::vec2) override;
float setDepth (Source *, float d = -1.f);
private:
float aspect_ratio;
Mesh *persp_layer_;
Mesh *persp_left_, *persp_right_;
Group *frame_;
};
class TransitionView : public View
@@ -188,6 +212,7 @@ public:
std::pair<Node *, glm::vec2> pick(glm::vec2 P) override;
Cursor grab (Source *s, glm::vec2 from, glm::vec2 to, std::pair<Node *, glm::vec2> pick) override;
void arrow (glm::vec2) override;
Cursor drag (glm::vec2, glm::vec2) override;
void attach(SessionSource *ts);
@@ -195,12 +220,78 @@ public:
void play(bool open);
private:
class Surface *output_surface_;
class Mesh *mark_100ms_, *mark_1s_;
Surface *output_surface_;
Mesh *mark_100ms_, *mark_1s_;
Switch *gradient_;
SessionSource *transition_source_;
};
class AppearanceView : public View
{
public:
AppearanceView();
void select(glm::vec2, glm::vec2) override;
void selectAll() override;
void draw () override;
void update (float dt) override;
void resize (int) override;
int size () override;
std::pair<Node *, glm::vec2> pick(glm::vec2 P) override;
Cursor grab (Source *s, glm::vec2 from, glm::vec2 to, std::pair<Node *, glm::vec2> pick) override;
Cursor over (glm::vec2) override;
void arrow (glm::vec2) override;
void initiate() override;
void terminate() override;
private:
Source *edit_source_;
bool need_edit_update_;
Source *getEditOrCurrentSource();
void adjustBackground();
Surface *preview_surface_;
class ImageShader *preview_shader_;
Surface *preview_checker_;
Frame *preview_frame_;
Surface *background_surface_;
Frame *background_frame_;
Mesh *horizontal_mark_;
Mesh *vertical_mark_;
bool show_scale_;
Group *mask_node_;
Frame *mask_square_;
Mesh *mask_circle_;
Mesh *mask_horizontal_;
Group *mask_vertical_;
Symbol *overlay_position_;
Symbol *overlay_position_cross_;
Symbol *overlay_scaling_;
Symbol *overlay_scaling_cross_;
Node *overlay_scaling_grid_;
Symbol *overlay_rotation_;
Symbol *overlay_rotation_fix_;
Node *overlay_rotation_clock_;
Symbol *overlay_rotation_clock_hand_;
bool show_context_menu_;
// for mask shader draw: 0=cursor, 1=brush, 2=eraser, 3=crop_shape
int mask_cursor_paint_;
int mask_cursor_shape_;
Mesh *mask_cursor_circle_;
Mesh *mask_cursor_square_;
Mesh *mask_cursor_crop_;
glm::vec3 stored_mask_size_;
bool show_cursor_forced_;
};
#endif // VIEW_H

View File

@@ -19,16 +19,23 @@ class LineCircle;
class Mesh;
class Frame;
class Handles;
class Symbol;
class Disk;
class Stream;
class MediaPlayer;
class Shader;
class ImageShader;
class MaskShader;
class ImageProcessingShader;
class Source;
class MediaSource;
class PatternSource;
class DeviceSource;
class GenericStreamSource;
class SessionSource;
class RenderSource;
class CloneSource;
class NetworkSource;
// Declares the interface for the visitors
class Visitor {
@@ -52,15 +59,22 @@ public:
virtual void visit (Mesh&) {}
virtual void visit (Frame&) {}
virtual void visit (Handles&) {}
virtual void visit (Symbol&) {}
virtual void visit (Disk&) {}
virtual void visit (Stream&) {}
virtual void visit (MediaPlayer&) {}
virtual void visit (Shader&) {}
virtual void visit (ImageShader&) {}
virtual void visit (MaskShader&) {}
virtual void visit (ImageProcessingShader&) {}
// utility
virtual void visit (Source&) {}
virtual void visit (MediaSource&) {}
virtual void visit (NetworkSource&) {}
virtual void visit (GenericStreamSource&) {}
virtual void visit (DeviceSource&) {}
virtual void visit (PatternSource&) {}
virtual void visit (SessionSource&) {}
virtual void visit (RenderSource&) {}
virtual void visit (CloneSource&) {}

View File

@@ -5,9 +5,9 @@
#define APP_TITLE " -- Video Live Mixer"
#define APP_SETTINGS "vimix.xml"
#define APP_VERSION_MAJOR 0
#define APP_VERSION_MINOR 2
#define APP_VERSION_MINOR 5
#define XML_VERSION_MAJOR 0
#define XML_VERSION_MINOR 1
#define XML_VERSION_MINOR 2
#define MAX_RECENT_HISTORY 20
#define MINI(a, b) (((a) < (b)) ? (a) : (b))
@@ -24,33 +24,53 @@
#define ROUND(val, factor) float( int( val * factor ) ) / factor;
#define SCENE_UNIT 5.f
#define SCENE_DEPTH 12.f
#define CIRCLE_SQUARE_DIST(x,y) ( (x*x + y*y) / (SCENE_UNIT * SCENE_UNIT * SCENE_UNIT * SCENE_UNIT) )
#define MIN_SCALE 0.01f
#define MAX_SCALE 10.f
#define CLAMP_SCALE(x) SIGN(x) * CLAMP( ABS(x), MIN_SCALE, MAX_SCALE)
#define SCENE_DEPTH 14.f
#define MIN_DEPTH 0.f
#define MAX_DEPTH 12.f
#define BACKGROUND_DEPTH 2.f
#define FOREGROUND_DEPTH 10.f
#define MIXING_DEFAULT_SCALE 2.4f
#define MIXING_MIN_SCALE 0.8f
#define MIXING_MAX_SCALE 7.0f
#define GEOMETRY_DEFAULT_SCALE 1.2f
#define GEOMETRY_MIN_SCALE 0.2f
#define GEOMETRY_MAX_SCALE 10.0f
#define LAYER_DEFAULT_SCALE 0.8f
#define MIXING_LIMBO_SCALE 1.3f
#define MIXING_ICON_SCALE 0.15f, 0.15f, 1.f
#define GEOMETRY_DEFAULT_SCALE 1.4f
#define GEOMETRY_MIN_SCALE 0.4f
#define GEOMETRY_MAX_SCALE 7.0f
#define LAYER_DEFAULT_SCALE 0.6f
#define LAYER_MIN_SCALE 0.4f
#define LAYER_MAX_SCALE 1.7f
#define TRANSITION_DEFAULT_SCALE 3.0f
#define LAYER_PERSPECTIVE 2.0f
#define APPEARANCE_DEFAULT_SCALE 2.f
#define APPEARANCE_MIN_SCALE 0.4f
#define APPEARANCE_MAX_SCALE 7.0f
#define BRUSH_MIN_SIZE 0.05f
#define BRUSH_MAX_SIZE 2.f
#define BRUSH_MIN_PRESS 0.005f
#define BRUSH_MAX_PRESS 1.f
#define SHAPE_MIN_BLUR 0.f
#define SHAPE_MAX_BLUR 1.f
#define TRANSITION_DEFAULT_SCALE 5.0f
#define TRANSITION_MIN_DURATION 0.2f
#define TRANSITION_MAX_DURATION 10.f
#define ARROWS_MOVEMENT_FACTOR 5.f
#define IMGUI_TITLE_MAINWINDOW ICON_FA_CIRCLE_NOTCH " vimix"
#define IMGUI_TITLE_MEDIAPLAYER ICON_FA_FILM " Player"
#define IMGUI_TITLE_HISTORY ICON_FA_HISTORY " History"
#define IMGUI_TITLE_TOOLBOX ICON_FA_WRENCH " Development Tools"
#define IMGUI_TITLE_SHADEREDITOR ICON_FA_CODE " Code"
#define IMGUI_TITLE_PREVIEW ICON_FA_DESKTOP " Ouput"
#define IMGUI_TITLE_DELETE ICON_FA_BROOM " Delete?"
#define IMGUI_LABEL_RECENT_FILES " Recent files"
#define IMGUI_LABEL_RECENT_FILES " Select recent"
#define IMGUI_RIGHT_ALIGN -3.5f * ImGui::GetTextLineHeightWithSpacing()
#define IMGUI_COLOR_OVERLAY IM_COL32(5, 5, 5, 150)
#define IMGUI_COLOR_RECORD 1.0, 0.05, 0.05
#define IMGUI_COLOR_STREAM 0.05, 0.8, 1.0
#define IMGUI_NOTIFICATION_DURATION 1.5f
#ifdef APPLE
#define CTRL_MOD "Cmd+"
@@ -64,42 +84,13 @@
#define COLOR_HIGHLIGHT_SOURCE 1.f, 1.f, 1.f
#define COLOR_TRANSITION_SOURCE 1.f, 0.5f, 1.f
#define COLOR_TRANSITION_LINES 0.9f, 0.9f, 0.9f
#define COLOR_APPEARANCE_SOURCE 0.9f, 0.9f, 0.1f
#define COLOR_APPEARANCE_MASK 0.1f, 0.9f, 0.9f
#define COLOR_APPEARANCE_MASK_DISABLE 0.3f, 0.6f, 0.6f
#define COLOR_FRAME 0.8f, 0.f, 0.8f
#define COLOR_FRAME_LIGHT 0.95f, 0.3f, 0.95f
#define COLOR_LIMBO_CIRCLE 0.16f, 0.16f, 0.16f
#define COLOR_SLIDER_CIRCLE 0.11f, 0.11f, 0.11f
// from glmixer
#define TEXTURE_REQUIRED_MAXIMUM 2048
#define CATALOG_TEXTURE_HEIGHT 96
#define SELECTBUFSIZE 512
#define CIRCLE_SIZE 8.0
#define DEFAULT_LIMBO_SIZE 1.5
#define MIN_LIMBO_SIZE 1.1
#define MAX_LIMBO_SIZE 3.0
#define DEFAULT_ICON_SIZE 1.75
#define MIN_ICON_SIZE 1.0
#define MAX_ICON_SIZE 2.5
#define MIN_DEPTH_LAYER 0.0
#define MAX_DEPTH_LAYER 40.0
#define DEPTH_EPSILON 0.1
#define DEPTH_DEFAULT_SPACING 1.0
#define BORDER_SIZE 0.4
#define CENTER_SIZE 1.2
#define PROPERTY_DECIMALS 8
#define COLOR_SOURCE 230, 230, 0
#define COLOR_SOURCE_STATIC 230, 40, 40
#define COLOR_SELECTION 10, 210, 40
#define COLOR_SELECTION_AREA 50, 210, 50
#define COLOR_CIRCLE 210, 30, 210
#define COLOR_CIRCLE_MOVE 230, 30, 230
#define COLOR_DRAWINGS 180, 180, 180
#define COLOR_LIMBO 35, 35, 35
//#define COLOR_LIMBO_CIRCLE 210, 160, 210
#define COLOR_FADING 25, 25, 25
#define COLOR_FLASHING 250, 250, 250
#define COLOR_FRAME_MOVE 230, 30, 230
#define COLOR_CURSOR 10, 100, 255
#define COLOR_STASH_CIRCLE 0.06f, 0.06f, 0.06f
#endif // VMIX_DEFINES_H

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 592 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 772 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 846 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 KiB

Some files were not shown because too many files have changed in this diff Show More