Compare commits
192 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1e81b58b1 | ||
|
|
cf2b6b4b39 | ||
|
|
c4e584a1da | ||
|
|
25b58b76f3 | ||
|
|
b346403887 | ||
|
|
e0cd560dfb | ||
|
|
4313e51530 | ||
|
|
e2bb90208e | ||
|
|
85d72a1c0e | ||
|
|
a073ab41dd | ||
|
|
34c24d99df | ||
|
|
69d9d9473b | ||
|
|
a58c06c617 | ||
|
|
b7a54d0512 | ||
|
|
1677582034 | ||
|
|
44b888fd04 | ||
|
|
78f9216d32 | ||
|
|
1ea0ec53af | ||
|
|
688823e63f | ||
|
|
78f4b80b84 | ||
|
|
bb8f536f0a | ||
|
|
ca51c5348e | ||
|
|
cc4d9ede97 | ||
|
|
053c2a3f1f | ||
|
|
1538e7b85b | ||
|
|
61d2a4dcb9 | ||
|
|
3c55e25432 | ||
|
|
93ad971fc0 | ||
|
|
5200de2e3e | ||
|
|
d92736b38f | ||
|
|
20f1320e2d | ||
|
|
b6af17f283 | ||
|
|
4a6a110e3d | ||
|
|
7f161a0a49 | ||
|
|
30301b51d4 | ||
|
|
c33796e97c | ||
|
|
61ee23455b | ||
|
|
1cd36b6134 | ||
|
|
59087f9198 | ||
|
|
bb231868b4 | ||
|
|
c841c0e342 | ||
|
|
4630d39663 | ||
|
|
3b529222d8 | ||
|
|
1ab2ae0df0 | ||
|
|
8c9b753544 | ||
|
|
196ce3df1b | ||
|
|
15a0bab925 | ||
|
|
95ed47934d | ||
|
|
8c2c2302d1 | ||
|
|
187a6132fc | ||
|
|
b3fd29056e | ||
|
|
11a58b5adf | ||
|
|
7c5374552d | ||
|
|
d33ff427b5 | ||
|
|
32a4607673 | ||
|
|
6f37ca8a84 | ||
|
|
f9dcd7348e | ||
|
|
f4baa67089 | ||
|
|
39f8b56c99 | ||
|
|
ea1192c334 | ||
|
|
56dfbc737d | ||
|
|
1c1a0e9b33 | ||
|
|
fcc014e5d1 | ||
|
|
bfb0576e26 | ||
|
|
71140a8c6c | ||
|
|
6d80c798f5 | ||
|
|
d3f6f2f87d | ||
|
|
d1050e9fdf | ||
|
|
105294fdaf | ||
|
|
4755f47286 | ||
|
|
77da91efa5 | ||
|
|
e679f18d93 | ||
|
|
c2531cf035 | ||
|
|
2124dfc718 | ||
|
|
ce5369a0ef | ||
|
|
ec797f8d67 | ||
|
|
ce7f30fa63 | ||
|
|
79482d3d1b | ||
|
|
93e7027f48 | ||
|
|
34580ab5ea | ||
|
|
bab0e9b710 | ||
|
|
88d4e3d9d5 | ||
|
|
47c338341d | ||
|
|
3cae0cd66f | ||
|
|
0738c25fb4 | ||
|
|
b8ebab5766 | ||
|
|
954b35032a | ||
|
|
46b9a8f663 | ||
|
|
41f87aa927 | ||
|
|
05a4ac164e | ||
|
|
44901b1e78 | ||
|
|
8ef79a6dbd | ||
|
|
940dd0f2a5 | ||
|
|
4fa7e06e19 | ||
|
|
7f2c3d531c | ||
|
|
a4621f31e3 | ||
|
|
7438b257ae | ||
|
|
cb6a0aefa4 | ||
|
|
7fba62bc49 | ||
|
|
01410a59cf | ||
|
|
e60c7a4cad | ||
|
|
8fa14bda1a | ||
|
|
469ee4c26a | ||
|
|
2627174fc0 | ||
|
|
7246dfa08e | ||
|
|
db0892d25b | ||
|
|
509416d5a0 | ||
|
|
43f444f07b | ||
|
|
bbeb99056a | ||
|
|
65aefc9fb8 | ||
|
|
27239b7513 | ||
|
|
15285ec151 | ||
|
|
d7893be541 | ||
|
|
59c07ceb96 | ||
|
|
007d876dbc | ||
|
|
3a41e59f00 | ||
|
|
3a34da9322 | ||
|
|
b3ee400b1a | ||
|
|
102413c7f4 | ||
|
|
c674fa0897 | ||
|
|
bd922f5bcc | ||
|
|
1390eff646 | ||
|
|
34b508a8dd | ||
|
|
8297c85220 | ||
|
|
795c0ed30f | ||
|
|
babbddcf28 | ||
|
|
650017c8f3 | ||
|
|
2c1eaff476 | ||
|
|
c0e135993c | ||
|
|
22011ffd54 | ||
|
|
31ebccd248 | ||
|
|
af11408ee9 | ||
|
|
99f5236959 | ||
|
|
67463d2214 | ||
|
|
3aabb83ccf | ||
|
|
82b755db84 | ||
|
|
10dc426119 | ||
|
|
233fc64c4e | ||
|
|
977ae76f9b | ||
|
|
77d9b17ac8 | ||
|
|
6f4b75ec1c | ||
|
|
2faa499ace | ||
|
|
2493d8d9f9 | ||
|
|
616c6c8bdf | ||
|
|
5421b5e926 | ||
|
|
d563ee14a9 | ||
|
|
3e5b1e74e8 | ||
|
|
f32b85a656 | ||
|
|
61e5c046c0 | ||
|
|
0eaffe213a | ||
|
|
b2a316b813 | ||
|
|
fac798df93 | ||
|
|
83a2da6b2b | ||
|
|
467ed23b37 | ||
|
|
41e0a6f0be | ||
|
|
00ebacc9db | ||
|
|
e0d44d4db1 | ||
|
|
e8a88fcbb9 | ||
|
|
d4b014188e | ||
|
|
b05207b27d | ||
|
|
c777a3d153 | ||
|
|
1bada746dc | ||
|
|
0d53afb6fd | ||
|
|
3b31d33c90 | ||
|
|
dcffb1cbaa | ||
|
|
bbd105983d | ||
|
|
1d7e0838fa | ||
|
|
d9a205d9ab | ||
|
|
39ceea9690 | ||
|
|
89891a18e5 | ||
|
|
2b59a0e6ed | ||
|
|
688aee8831 | ||
|
|
047163a38c | ||
|
|
df2a66484b | ||
|
|
69c74aa103 | ||
|
|
a4ff2a325f | ||
|
|
7109b94484 | ||
|
|
202db9eaa2 | ||
|
|
84caf2da9a | ||
|
|
9e160fec51 | ||
|
|
b7d54dfadf | ||
|
|
04e03456bf | ||
|
|
e84b16c9ce | ||
|
|
9251aff19f | ||
|
|
519baf7a3b | ||
|
|
59db2cf57c | ||
|
|
db6d3a6fa0 | ||
|
|
1209d337bc | ||
|
|
b0e54c6ff5 | ||
|
|
76f067de55 | ||
|
|
9e32e4f5b2 | ||
|
|
76926f433c |
2
.gitignore
vendored
@@ -14,3 +14,5 @@ libTINYXML2.a
|
||||
libvmix-resources.a
|
||||
rules.ninja
|
||||
/vmix
|
||||
/vimix_*.snap
|
||||
/CMakeLists.txt.user.*
|
||||
|
||||
255
ActionManager.cpp
Normal 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
@@ -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
|
||||
109
CMakeLists.txt
@@ -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,11 @@ set(THREAD_LIBRARY Threads::Threads)
|
||||
find_package(PNG REQUIRED)
|
||||
set(PNG_LIBRARY PNG::PNG)
|
||||
|
||||
#set(ICU_ROOT "/usr/local/Cellar/icu4c/67.1")
|
||||
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 +154,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 +231,8 @@ include_directories(
|
||||
${TINYFD_INCLUDE_DIR}
|
||||
${STB_INCLUDE_DIR}
|
||||
${DIRENT_INCLUDE_DIR}
|
||||
${OBJLOADER_INCLUDE_DIR}
|
||||
${OSCPACK_INCLUDE_DIR}
|
||||
${ICU_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
|
||||
@@ -229,14 +257,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 +286,21 @@ 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_lowleftcorner.fs
|
||||
./rsc/shaders/mask_uprightcorner.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,6 +345,9 @@ 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
|
||||
@@ -306,6 +357,9 @@ set(VMIX_RSC_FILES
|
||||
./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 +373,7 @@ 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/h_line.ply
|
||||
./rsc/mesh/h_mark.ply
|
||||
)
|
||||
@@ -336,6 +391,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 +400,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 +412,9 @@ ELSE(APPLE)
|
||||
${IMGUITEXTEDIT_SRC}
|
||||
)
|
||||
|
||||
set(PLATFORM_LIBS ""
|
||||
)
|
||||
|
||||
ENDIF(APPLE)
|
||||
|
||||
|
||||
@@ -365,7 +429,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 +449,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 +468,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 +481,8 @@ SET(CPACK_SOURCE_IGNORE_FILES
|
||||
|
||||
IF(APPLE)
|
||||
|
||||
# include( InstallRequiredSystemLibraries )
|
||||
|
||||
# Bundle target
|
||||
set(CPACK_GENERATOR "DragNDrop")
|
||||
|
||||
@@ -433,8 +504,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 +518,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
@@ -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
@@ -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
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -310,20 +350,27 @@ Symbol::Symbol(Type t, glm::vec3 pos) : Node(), type_(t)
|
||||
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[PATTERN] = new Mesh("mesh/icon_gear.ply");
|
||||
icons[CAMERA] = new Mesh("mesh/icon_camera.ply");
|
||||
icons[SHARE] = new Mesh("mesh/icon_share.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[ARROWS] = new Mesh("mesh/icon_rightarrow.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");
|
||||
icons[EMPTY] = new Mesh("mesh/icon_empty.ply");
|
||||
}
|
||||
|
||||
static Mesh *shadow= new Mesh("mesh/border_handles_shadow.ply", "images/soft_shadow.dds");
|
||||
|
||||
symbol_ = icons[type_];
|
||||
shadow_ = shadow;
|
||||
translation_ = pos;
|
||||
color = glm::vec4( 1.f, 1.f, 1.f, 1.f);
|
||||
}
|
||||
@@ -338,6 +385,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 +413,7 @@ void Symbol::draw(glm::mat4 modelview, glm::mat4 projection)
|
||||
// generate matrix
|
||||
ctm = GlmToolkit::transform(tran, rot, sca);
|
||||
|
||||
shadow_->draw( ctm, projection );
|
||||
symbol_->draw( ctm, projection);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
@@ -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>();
|
||||
@@ -26,7 +26,7 @@ void DrawVisitor::loop(int num, glm::mat4 transform)
|
||||
void DrawVisitor::visit(Node &n)
|
||||
{
|
||||
// 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
|
||||
@@ -52,7 +52,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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
#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>
|
||||
|
||||
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 +24,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 +64,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 +95,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)
|
||||
@@ -114,6 +146,18 @@ 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);
|
||||
@@ -223,3 +267,21 @@ 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,11 +8,12 @@ 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();
|
||||
|
||||
@@ -24,7 +25,6 @@ public:
|
||||
void begin();
|
||||
// pop attrib and unbind to end draw
|
||||
void end();
|
||||
|
||||
// blit copy to another, returns true on success
|
||||
bool blit(FrameBuffer *other);
|
||||
// bind the FrameBuffer in READ and perform glReadPixels
|
||||
@@ -40,6 +40,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_; }
|
||||
@@ -53,6 +59,8 @@ private:
|
||||
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
@@ -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
@@ -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
|
||||
@@ -34,7 +34,7 @@ void GarbageVisitor::visit(Node &n)
|
||||
|
||||
// take the node out of the Tree
|
||||
if (current_)
|
||||
current_->detatch(&n);
|
||||
current_->detach(&n);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
11
GlmToolkit.h
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
140
GstToolkit.cpp
@@ -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)
|
||||
@@ -122,121 +124,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,14 +18,13 @@ typedef enum {
|
||||
std::string time_to_string(guint64 t, time_string_mode m = TIME_STRING_ADJUSTED);
|
||||
|
||||
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_
|
||||
|
||||
@@ -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"
|
||||
@@ -37,7 +39,7 @@ void ImGuiToolkit::ButtonOpenUrl( const char* url, const ImVec2& size_arg )
|
||||
}
|
||||
|
||||
|
||||
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 +48,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 +124,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 +137,13 @@ 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 && ImGui::IsItemHovered())
|
||||
{
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::Text("%s", tooltip);
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -262,9 +273,9 @@ void ImGuiToolkit::ShowIconsWindow(bool* p_open)
|
||||
|
||||
// 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();
|
||||
@@ -609,9 +620,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 +646,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 +715,7 @@ bool ImGuiToolkit::EditPlotHistoLines(const char* label, float *histogram_array,
|
||||
active = false;
|
||||
ImGui::ClearActiveID();
|
||||
previous_index = UINT32_MAX;
|
||||
*released = true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -732,15 +747,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 +805,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 +825,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;
|
||||
|
||||
@@ -15,21 +15,21 @@ 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 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 +56,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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
364
ImGuiVisitor.cpp
@@ -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,52 +171,87 @@ 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);
|
||||
@@ -205,6 +259,7 @@ void ImGuiVisitor::visit(ImageProcessingShader &n)
|
||||
if (ImGuiToolkit::ButtonIcon(4, 1)) {
|
||||
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(2, 1)) {
|
||||
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(7, 1)) {
|
||||
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,40 @@ 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...
|
||||
|
||||
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);
|
||||
|
||||
// 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 +430,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 +447,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 +466,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 +482,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 +491,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());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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]
|
||||
|
||||
105
ImageShader.cpp
@@ -4,27 +4,21 @@
|
||||
#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[6] = { ICON_FA_EXPAND,
|
||||
ICON_FA_CIRCLE,
|
||||
ICON_FA_MINUS_CIRCLE,
|
||||
ICON_FA_SQUARE,
|
||||
ICON_FA_CARET_SQUARE_RIGHT,
|
||||
ICON_FA_CARET_SQUARE_LEFT };
|
||||
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 +32,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 +45,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 +63,62 @@ 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/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_lowleftcorner.fs"));
|
||||
mask_programs.push_back(new ShadingProgram("shaders/simple.vs", "shaders/mask_uprightcorner.fs"));
|
||||
}
|
||||
// reset instance
|
||||
reset();
|
||||
// static program shader
|
||||
program_ = mask_programs[0];
|
||||
}
|
||||
|
||||
void MaskShader::use()
|
||||
{
|
||||
// select program to use
|
||||
mode = CLAMP(mode, 0, mask_programs.size()-1);
|
||||
program_ = mask_programs[mode];
|
||||
|
||||
// actual use of shader program
|
||||
Shader::use();
|
||||
|
||||
// set parameters
|
||||
program_->setUniform("blur", blur);
|
||||
program_->setUniform("size", size);
|
||||
}
|
||||
|
||||
void MaskShader::reset()
|
||||
{
|
||||
Shader::reset();
|
||||
|
||||
// default mask
|
||||
mode = 0;
|
||||
blur = 0.5f;
|
||||
size = glm::vec2(1.f, 1.f);
|
||||
}
|
||||
|
||||
void MaskShader::operator = (const MaskShader &S)
|
||||
{
|
||||
Shader::operator =(S);
|
||||
|
||||
mode = S.mode;
|
||||
blur = S.blur;
|
||||
size = S.size;
|
||||
}
|
||||
|
||||
|
||||
void MaskShader::accept(Visitor& v) {
|
||||
Shader::accept(v);
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#ifndef IMAGESHADER_H
|
||||
#ifndef IMAGESHADER_H
|
||||
#define IMAGESHADER_H
|
||||
|
||||
#include <string>
|
||||
@@ -16,20 +16,39 @@ 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);
|
||||
|
||||
uint mode;
|
||||
|
||||
// uniforms
|
||||
float blur;
|
||||
glm::vec2 size;
|
||||
|
||||
static const char* mask_names[6];
|
||||
static std::vector< ShadingProgram* > mask_programs;
|
||||
};
|
||||
|
||||
#endif // IMAGESHADER_H
|
||||
|
||||
241
Loopback.cpp
Normal 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
@@ -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
|
||||
109
MediaPlayer.cpp
@@ -12,6 +12,7 @@ using namespace std;
|
||||
#include "Resource.h"
|
||||
#include "Visitor.h"
|
||||
#include "SystemToolkit.h"
|
||||
#include "GlmToolkit.h"
|
||||
|
||||
#include "MediaPlayer.h"
|
||||
|
||||
@@ -26,12 +27,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 +75,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 +107,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 +134,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 +172,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;
|
||||
}
|
||||
@@ -205,11 +205,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 +217,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 +321,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 +357,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 +386,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 +441,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 +488,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 +617,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 +645,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 +728,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 +749,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 +779,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 +825,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 +888,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 +951,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
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
367
Mixer.cpp
@@ -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,28 @@ 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::unsetCurrentSource()
|
||||
@@ -522,6 +698,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 +708,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 +722,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 +743,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();
|
||||
@@ -575,10 +757,10 @@ void Mixer::load(const std::string& filename)
|
||||
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 +795,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 +810,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 +826,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 +834,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 +850,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 +861,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());
|
||||
}
|
||||
@@ -731,7 +904,7 @@ void Mixer::clear()
|
||||
void Mixer::set(Session *s)
|
||||
{
|
||||
if ( s == nullptr ) {
|
||||
Log::Warning("Failed to load Session.");
|
||||
Log::Warning("Session loading cancelled.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -745,3 +918,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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
28
Mixer.h
@@ -36,34 +36,47 @@ 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 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 +91,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 +103,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 +114,7 @@ protected:
|
||||
MixingView mixing_;
|
||||
GeometryView geometry_;
|
||||
LayerView layer_;
|
||||
AppearanceView appearance_;
|
||||
TransitionView transition_;
|
||||
|
||||
guint64 update_time_;
|
||||
|
||||
316
NetworkSource.cpp
Normal 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
@@ -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
@@ -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
@@ -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
|
||||
160
PatternSource.cpp
Normal file
@@ -0,0 +1,160 @@
|
||||
#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 23
|
||||
|
||||
// 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_[24] = { "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",
|
||||
"Blob",
|
||||
"Timer",
|
||||
"Clock"
|
||||
};
|
||||
|
||||
Pattern::Pattern() : Stream(), type_(MAX_PATTERN+1) // invalid pattern
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
glm::ivec2 Pattern::resolution()
|
||||
{
|
||||
return glm::ivec2( width_, height_);
|
||||
}
|
||||
|
||||
|
||||
void Pattern::open( uint pattern, glm::ivec2 res )
|
||||
{
|
||||
type_ = MIN(pattern, MAX_PATTERN);
|
||||
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
@@ -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
|
||||
@@ -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
|
||||
@@ -147,15 +146,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 vec = glm::inverse(modelview_) * glm::vec4( 0.12f, 0.12f, 0.f, 0.f );
|
||||
picked = glm::length( glm::vec2( 1.f, 1.f) + glm::vec2(vec) - 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 vec = glm::inverse(modelview_) * glm::vec4( 0.12f, -0.12f, 0.f, 0.f );
|
||||
picked = glm::length( glm::vec2( 1.f, -1.f) + glm::vec2(vec) - 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 vec = glm::inverse(modelview_) * glm::vec4( 0.12f, 0.12f, 0.f, 0.f );
|
||||
picked = glm::length( glm::vec2( -1.f, -1.f) + glm::vec2(vec) - 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 vec = glm::inverse(modelview_) * glm::vec4( -0.12f, 0.12f, 0.f, 0.f );
|
||||
picked = glm::length( glm::vec2( -1.f, 1.f) + glm::vec2(vec) - glm::vec2(P) ) < 1.5f * scale;
|
||||
}
|
||||
|
||||
if ( picked )
|
||||
@@ -165,6 +172,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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() )
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
521
Recorder.cpp
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
72
Recorder.h
@@ -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;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -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,13 @@ 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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -398,7 +408,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 +422,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 +443,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 +512,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 +591,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 +613,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 +629,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 +683,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 +701,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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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_; }
|
||||
|
||||
|
||||
31
Resource.cpp
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
41
Scene.cpp
@@ -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
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@ public:
|
||||
bool empty();
|
||||
uint size ();
|
||||
|
||||
std::string xml();
|
||||
|
||||
protected:
|
||||
SourceList::iterator find (Source *s);
|
||||
SourceList selection_;
|
||||
|
||||
185
Session.cpp
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
25
Session.h
@@ -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
|
||||
|
||||
@@ -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,264 @@ 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;
|
||||
// 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("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 +301,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 +343,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 +376,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 +385,27 @@ 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);
|
||||
|
||||
XMLElement* uniforms = xmlCurrent_->FirstChildElement("uniforms");
|
||||
if (uniforms) {
|
||||
uniforms->QueryFloatAttribute("blur", &n.blur);
|
||||
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 +436,101 @@ 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_) s.maskShader()->accept(*this);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
|
||||
@@ -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,34 @@ 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 textured type
|
||||
xmlCurrent_->SetAttribute("type", "MaskShader");
|
||||
xmlCurrent_->SetAttribute("id", n.id());
|
||||
xmlCurrent_->SetAttribute("mode", n.mode);
|
||||
|
||||
XMLElement *uniforms = xmlDoc_->NewElement("uniforms");
|
||||
uniforms->SetAttribute("blur", n.blur);
|
||||
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 +238,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 +288,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 +339,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 +357,18 @@ 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);
|
||||
|
||||
xmlCurrent_ = xmlDoc_->NewElement( "ImageProcessing" );
|
||||
xmlCurrent_->SetAttribute("enabled", s.imageProcessingEnabled());
|
||||
sourceNode->InsertEndChild(xmlCurrent_);
|
||||
@@ -365,7 +399,7 @@ void SessionVisitor::visit (SessionSource& s)
|
||||
path->InsertEndChild( text );
|
||||
}
|
||||
|
||||
void SessionVisitor::visit (RenderSource& s)
|
||||
void SessionVisitor::visit (RenderSource&)
|
||||
{
|
||||
xmlCurrent_->SetAttribute("type", "RenderSource");
|
||||
}
|
||||
@@ -379,3 +413,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() );
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
115
Settings.cpp
@@ -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,6 +103,28 @@ 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" );
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
61
Settings.h
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
24
Shader.cpp
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
21
Shader.h
@@ -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;
|
||||
|
||||
};
|
||||
|
||||
|
||||
307
Source.cpp
@@ -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), 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,20 +130,82 @@ 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;
|
||||
|
||||
// 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;
|
||||
|
||||
}
|
||||
|
||||
@@ -147,6 +221,8 @@ Source::~Source()
|
||||
delete stored_status_;
|
||||
if (renderbuffer_)
|
||||
delete renderbuffer_;
|
||||
if (maskbuffer_)
|
||||
delete maskbuffer_;
|
||||
|
||||
// all groups and their children are deleted in the scene
|
||||
// this includes rendersurface_, overlays, blendingshader_ and rendershader_
|
||||
@@ -154,6 +230,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 +241,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 +258,6 @@ void Source::accept(Visitor& v)
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
|
||||
Source::Mode Source::mode() const
|
||||
{
|
||||
return mode_;
|
||||
@@ -193,16 +271,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 +316,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,55 +324,74 @@ 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 icons
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
// (re) create the masking buffer
|
||||
if (maskbuffer_)
|
||||
delete maskbuffer_;
|
||||
maskbuffer_ = new FrameBuffer( renderbuffer->resolution() );
|
||||
|
||||
// make the source visible
|
||||
if ( mode_ == UNINITIALIZED )
|
||||
setMode(VISIBLE);
|
||||
|
||||
// request update
|
||||
need_update_ = true;
|
||||
}
|
||||
|
||||
|
||||
void Source::setActive (bool on)
|
||||
{
|
||||
active_ = on;
|
||||
@@ -325,40 +425,79 @@ 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 );
|
||||
mixingshader_->color.a = blendingshader_->color.a;
|
||||
|
||||
// CHANGE update status based on limbo
|
||||
bool a = glm::length(dist) < 1.3f;
|
||||
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);
|
||||
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_);
|
||||
|
||||
// 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;
|
||||
|
||||
// draw nask in mask frame buffer
|
||||
maskbuffer_->begin();
|
||||
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
|
||||
@@ -422,17 +561,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 +580,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 +593,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 +623,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);
|
||||
}
|
||||
|
||||
|
||||
69
Source.h
@@ -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,20 +67,23 @@ 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 mask
|
||||
inline MaskShader *maskShader () const { return maskshader_; }
|
||||
|
||||
// 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;
|
||||
|
||||
@@ -84,7 +91,7 @@ public:
|
||||
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);
|
||||
@@ -94,13 +101,13 @@ public:
|
||||
inline bool active () { return active_; }
|
||||
|
||||
// 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);
|
||||
@@ -123,11 +130,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 +163,25 @@ 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_;
|
||||
|
||||
// surface to draw on
|
||||
Surface *texturesurface_;
|
||||
|
||||
// mode for display
|
||||
Mode mode_;
|
||||
@@ -162,7 +189,8 @@ 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_;
|
||||
|
||||
// update
|
||||
bool active_;
|
||||
@@ -185,7 +213,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 +221,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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
@@ -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_67::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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -113,15 +115,9 @@ class UserInterface
|
||||
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,6 +163,7 @@ protected:
|
||||
void selectOpenFilename();
|
||||
|
||||
void RenderPreview();
|
||||
void RenderHistory();
|
||||
void RenderShaderEditor();
|
||||
void handleKeyboard();
|
||||
void handleMouse();
|
||||
|
||||
91
View.h
@@ -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() {}
|
||||
@@ -24,6 +30,8 @@ public:
|
||||
virtual void draw ();
|
||||
|
||||
virtual void zoom (float) {}
|
||||
virtual void resize (int) {}
|
||||
virtual int size () { return 0; }
|
||||
virtual void recenter();
|
||||
virtual void centerSource(Source *) {}
|
||||
|
||||
@@ -58,7 +66,7 @@ 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();
|
||||
}
|
||||
@@ -72,13 +80,15 @@ public:
|
||||
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_;
|
||||
};
|
||||
|
||||
@@ -91,6 +101,8 @@ 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 selectAll() override;
|
||||
|
||||
@@ -109,7 +121,9 @@ private:
|
||||
class Disk *slider_;
|
||||
class Disk *button_white_;
|
||||
class Disk *button_black_;
|
||||
class Disk *stashCircle_;
|
||||
class Mesh *mixingCircle_;
|
||||
|
||||
};
|
||||
|
||||
class RenderView : public View
|
||||
@@ -140,6 +154,8 @@ 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;
|
||||
@@ -157,6 +173,8 @@ private:
|
||||
Node *overlay_scaling_;
|
||||
Node *overlay_scaling_cross_;
|
||||
Node *overlay_scaling_grid_;
|
||||
Node *overlay_crop_;
|
||||
bool show_context_menu_;
|
||||
};
|
||||
|
||||
class LayerView : public View
|
||||
@@ -166,6 +184,8 @@ public:
|
||||
|
||||
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;
|
||||
@@ -195,12 +215,73 @@ 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 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 drag (glm::vec2, glm::vec2) 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_corner_;
|
||||
class Handles *mask_handle_;
|
||||
|
||||
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_;
|
||||
|
||||
|
||||
// /// tests
|
||||
// Surface *preview_mask_;
|
||||
// Surface *test_surface;
|
||||
// class MaskShader *test_shader;
|
||||
// FrameBuffer *test_buffer;
|
||||
};
|
||||
|
||||
|
||||
#endif // VIEW_H
|
||||
|
||||
14
Visitor.h
@@ -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&) {}
|
||||
|
||||
52
defines.h
@@ -5,7 +5,7 @@
|
||||
#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 4
|
||||
#define XML_VERSION_MAJOR 0
|
||||
#define XML_VERSION_MINOR 1
|
||||
#define MAX_RECENT_HISTORY 20
|
||||
@@ -32,25 +32,32 @@
|
||||
#define MIXING_DEFAULT_SCALE 2.4f
|
||||
#define MIXING_MIN_SCALE 0.8f
|
||||
#define MIXING_MAX_SCALE 7.0f
|
||||
#define MIXING_ICON_SCALE 0.15f, 0.15f, 1.f
|
||||
#define GEOMETRY_DEFAULT_SCALE 1.2f
|
||||
#define GEOMETRY_MIN_SCALE 0.2f
|
||||
#define GEOMETRY_MIN_SCALE 0.4f
|
||||
#define GEOMETRY_MAX_SCALE 10.0f
|
||||
#define LAYER_DEFAULT_SCALE 0.8f
|
||||
#define LAYER_MIN_SCALE 0.4f
|
||||
#define LAYER_MAX_SCALE 1.7f
|
||||
#define TRANSITION_DEFAULT_SCALE 3.0f
|
||||
#define APPEARANCE_DEFAULT_SCALE 2.f
|
||||
#define APPEARANCE_MIN_SCALE 0.4f
|
||||
#define APPEARANCE_MAX_SCALE 8.0f
|
||||
#define TRANSITION_DEFAULT_SCALE 5.0f
|
||||
#define TRANSITION_MIN_DURATION 0.2f
|
||||
#define TRANSITION_MAX_DURATION 10.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 +71,11 @@
|
||||
#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_FRAME 0.8f, 0.f, 0.8f
|
||||
#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
|
||||
|
||||
BIN
docs/images/Screenshot-stream-confirm.png
Normal file
|
After Width: | Height: | Size: 350 KiB |
BIN
docs/images/Screenshot-stream-open.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
docs/images/Screenshot-stream-out.png
Normal file
|
After Width: | Height: | Size: 356 KiB |
BIN
docs/images/manual_appearance_0.png
Normal file
|
After Width: | Height: | Size: 592 KiB |
BIN
docs/images/manual_appearance_1.png
Normal file
|
After Width: | Height: | Size: 772 KiB |
BIN
docs/images/manual_appearance_2.png
Normal file
|
After Width: | Height: | Size: 210 KiB |
BIN
docs/images/manual_appearance_3.png
Normal file
|
After Width: | Height: | Size: 846 KiB |
BIN
docs/images/manual_appearance_4.png
Normal file
|
After Width: | Height: | Size: 404 KiB |
BIN
docs/images/manual_appearance_5.png
Normal file
|
After Width: | Height: | Size: 582 KiB |
BIN
docs/images/manual_filters_0.png
Normal file
|
After Width: | Height: | Size: 344 KiB |