Compare commits

...

44 Commits
0.3.2 ... 0.4

Author SHA1 Message Date
brunoherbelin
ce5369a0ef Default settings to not accept connections 2020-11-03 22:31:40 +01:00
brunoherbelin
ec797f8d67 Various GUI terminology changes for unified wording and clarity 2020-11-03 21:52:03 +01:00
brunoherbelin
ce7f30fa63 Minor GUI improvement connection 2020-11-03 19:13:20 +01:00
brunoherbelin
79482d3d1b Offer to reconnect a network source at anytime (there is no way to
really know if it was disconnected)
2020-11-03 18:44:12 +01:00
brunoherbelin
93e7027f48 Fixed Connection and Streamer mechanisms 2020-11-03 18:34:38 +01:00
brunoherbelin
34580ab5ea Fixup OSX system session file loading request 2020-11-03 17:56:18 +01:00
brunoherbelin
bab0e9b710 OSX support for 'OpenFile' system message (aka open vimix when selecting
session file in finder).
2020-11-02 20:55:38 +01:00
brunoherbelin
88d4e3d9d5 Added support for filename argument when running vimix (open session) 2020-11-01 23:59:24 +01:00
brunoherbelin
47c338341d Fix exit crash 2020-11-01 23:49:36 +01:00
brunoherbelin
3cae0cd66f Improved memory management Stream 2020-11-01 23:33:28 +01:00
brunoherbelin
0738c25fb4 Fix memory leak stream UDP 2020-11-01 23:32:40 +01:00
brunoherbelin
b8ebab5766 Fixing Streamer and NetworkSource dialog 2020-11-01 18:13:37 +01:00
brunoherbelin
954b35032a Fix Connection broadcaster 2020-11-01 15:11:26 +01:00
brunoherbelin
46b9a8f663 Fixed fullscreen main window for OSX 2020-11-01 13:18:49 +01:00
brunoherbelin
41f87aa927 Fix OSX fullscreen crash 2020-11-01 11:00:46 +01:00
brunoherbelin
05a4ac164e Merge branch 'master' of github.com:brunoherbelin/vimix 2020-10-31 19:23:47 +01:00
brunoherbelin
44901b1e78 various minor OSX compilation update 2020-10-31 19:21:21 +01:00
brunoherbelin
8ef79a6dbd Added frame buffer information display in session preview 2020-10-31 19:21:05 +01:00
brunoherbelin
940dd0f2a5 Using OSX avenc hardware decoder 2020-10-31 19:19:34 +01:00
brunoherbelin
4fa7e06e19 oops missing include 2020-10-26 21:43:33 +01:00
brunoherbelin
7f2c3d531c OSX compatibility posix for NetworkToolkit 2020-10-26 21:40:21 +01:00
brunoherbelin
a4621f31e3 OSX compilation compatibility 2020-10-26 19:32:19 +01:00
brunoherbelin
7438b257ae Added icon for NetworkSource (sharing logo) 2020-10-25 23:26:04 +01:00
brunoherbelin
cb6a0aefa4 Minor improvements in connection and IPC (multiple instances not fully
supported yet)
2020-10-25 23:14:47 +01:00
brunoherbelin
7fba62bc49 minor rename 2020-10-25 22:03:49 +01:00
brunoherbelin
01410a59cf Improved connection robustness and diconnection/connection behaviors
(added connection rejection to streamer).
2020-10-25 22:01:04 +01:00
brunoherbelin
e60c7a4cad Managing client name and disconnection (e.g. end vimix) 2020-10-25 18:56:56 +01:00
brunoherbelin
8fa14bda1a Fixing bugs with Network source ans Video Streamer. 2020-10-25 14:31:27 +01:00
brunoherbelin
469ee4c26a Finalizing NetworkSource (Visitors) 2020-10-25 10:00:32 +01:00
brunoherbelin
2627174fc0 To confirm: working implementation of SHM and UDP streaming connection 2020-10-25 00:23:44 +02:00
brunoherbelin
7246dfa08e Work-in progress: connection manager now used in Streamer and
NetworkSource to identify possible connections, and exchange streaming
configuration.
2020-10-24 23:53:11 +02:00
brunoherbelin
db0892d25b Defining a name for a Connection 2020-10-23 21:54:45 +02:00
brunoherbelin
509416d5a0 Connection manager seems to work... 2020-10-23 19:01:44 +02:00
brunoherbelin
43f444f07b Creation of the Connection Manager : this new mechanism continuously
checks for the presence of vimix programs in the network neibourhood.
The list of connections can then be used for indentifying streaming
requests and offers.
2020-10-23 01:02:28 +02:00
brunoherbelin
bbeb99056a Update to OSCPack v1.1 2020-10-20 18:27:26 +02:00
brunoherbelin
65aefc9fb8 Complete integration of original OSCPack lib 2020-10-20 18:18:44 +02:00
brunoherbelin
27239b7513 working on streaming and clients 2020-10-20 00:28:44 +02:00
brunoherbelin
15285ec151 Added lib OSCPack 2020-10-20 00:28:16 +02:00
brunoherbelin
d7893be541 First working implementation of Streamer, with TCP and SharedMemory. 2020-10-18 13:13:07 +02:00
brunoherbelin
59c07ceb96 First working implementation of VideoStreamer 2020-10-17 11:32:29 +02:00
brunoherbelin
007d876dbc Initial commit of Video Streamer. bugFix delete Pbos. 2020-10-14 22:58:02 +02:00
brunoherbelin
3a41e59f00 Management of recorders by id in user interface. 2020-10-14 22:37:53 +02:00
brunoherbelin
3a34da9322 Renaming Recorder to FrameGrabber 2020-10-14 21:04:22 +02:00
brunoherbelin
b3ee400b1a Hack to prevent re-openning automatically a session file in case vimix
was not properly closed (to avoid crash at start that prevent vimix from
restarting after loading a faulty session file).
2020-10-13 23:42:33 +02:00
73 changed files with 8046 additions and 246 deletions

View File

@@ -13,10 +13,10 @@ if(UNIX)
# the RPATH to be used when installing
set(CMAKE_SKIP_RPATH TRUE)
set(OpenGL_DIR /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/)
set(CMAKE_OSX_ARCHITECTURES x86_64)
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13")
# set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13" CACHE STRING "Minimum OS X version to target for deployment")
else()
add_definitions(-DLINUX)
@@ -149,6 +149,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 +226,7 @@ include_directories(
${TINYFD_INCLUDE_DIR}
${STB_INCLUDE_DIR}
${DIRENT_INCLUDE_DIR}
${OBJLOADER_INCLUDE_DIR}
${OSCPACK_INCLUDE_DIR}
)
@@ -230,6 +252,7 @@ set(VMIX_SRCS
SessionCreator.cpp
Mixer.cpp
Recorder.cpp
Streamer.cpp
Settings.cpp
Screenshot.cpp
Resource.cpp
@@ -241,6 +264,7 @@ set(VMIX_SRCS
StreamSource.cpp
PatternSource.cpp
DeviceSource.cpp
NetworkSource.cpp
FrameBuffer.cpp
RenderingManager.cpp
UserInterfaceManager.cpp
@@ -254,9 +278,12 @@ 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
@@ -313,6 +340,7 @@ set(VMIX_RSC_FILES
./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
@@ -343,6 +371,7 @@ IF(APPLE)
# create the application
add_executable(${VMIX_BINARY} MACOSX_BUNDLE
${VMIX_SRCS}
./osx/CustomDelegate.m
${IMGUITEXTEDIT_SRC}
${MACOSX_BUNDLE_ICON_FILE}
)
@@ -351,6 +380,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}
@@ -358,6 +392,9 @@ ELSE(APPLE)
${IMGUITEXTEDIT_SRC}
)
set(PLATFORM_LIBS ""
)
ENDIF(APPLE)
@@ -392,7 +429,9 @@ target_link_libraries(${VMIX_BINARY} LINK_PRIVATE
TINYXML2
TINYFD
IMGUI
OSCPACK
vmix::rc
${PLATFORM_LIBS}
)
macro_display_feature_log()
@@ -406,7 +445,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/"
@@ -443,7 +482,7 @@ IF(APPLE)
### 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
@@ -454,11 +493,11 @@ 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(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/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-bad
install(FILES "/Users/herbelin/Development/gst/gst-plugins-bad-1.18.0/build/ext/libde265/libgstde265.dylib" DESTINATION "${plugin_dest_dir}/gstreamer-1.0" COMPONENT Runtime)

292
Connection.cpp Normal file
View File

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

97
Connection.h Normal file
View File

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

View File

@@ -312,6 +312,7 @@ Symbol::Symbol(Type t, glm::vec3 pos) : Node(), type_(t)
icons[RENDER] = new Mesh("mesh/icon_render.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");

View File

@@ -60,7 +60,7 @@ protected:
class Symbol : public Node
{
public:
typedef enum { CIRCLE_POINT = 0, SQUARE_POINT, IMAGE, VIDEO, SESSION, CLONE, RENDER, PATTERN, CAMERA,
typedef enum { CIRCLE_POINT = 0, SQUARE_POINT, IMAGE, VIDEO, SESSION, CLONE, RENDER, PATTERN, CAMERA, SHARE,
DOTS, BUSY, LOCK, UNLOCK, CIRCLE, SQUARE, CLOCK, CLOCK_H, GRID, CROSS, EMPTY } Type;
Symbol(Type t = CIRCLE_POINT, glm::vec3 pos = glm::vec3(0.f));
~Symbol();

View File

@@ -19,6 +19,7 @@
#ifndef NDEBUG
#define DEVICE_DEBUG
//#define GST_DEVICE_DEBUG
#endif
@@ -112,7 +113,7 @@ Device::callback_device_monitor (GstBus *, GstMessage * message, gpointer )
break;
manager().src_name_.push_back(name);
#ifdef DEVICE_DEBUG
#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);
@@ -134,7 +135,7 @@ Device::callback_device_monitor (GstBus *, GstMessage * message, gpointer )
gst_message_parse_device_removed (message, &device);
name = gst_device_get_display_name (device);
manager().remove(name);
#ifdef DEVICE_DEBUG
#ifdef GST_DEVICE_DEBUG
g_print("\nDevice %s unplugged\n", name);
#endif
g_free (name);
@@ -204,7 +205,7 @@ Device::Device()
src_name_.push_back(name);
g_free (name);
#ifdef DEVICE_DEBUG
#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);
@@ -219,7 +220,7 @@ Device::Device()
g_list_free(devices);
// Add config for plugged screen
src_name_.push_back("Screen");
src_name_.push_back("Screen capture");
src_description_.push_back(gst_plugin_vidcap);
// Try to auto find resolution
@@ -379,7 +380,7 @@ void DeviceSource::setDevice(const std::string &devicename)
pipeline << " ! jpegdec";
if ( device_.find("Screen") != std::string::npos )
pipeline << " ! videoconvert ! video/x-raw,format=RGB ! queue";
pipeline << " ! videoconvert ! video/x-raw,format=RGB ! queue max-size-buffers=3";
pipeline << " ! videoconvert";
@@ -441,7 +442,7 @@ DeviceConfigSet Device::getDeviceConfigs(const std::string &src_description)
for (int c = 0; c < C; ++c) {
// get GST cap
GstStructure *decice_cap_struct = gst_caps_get_structure (device_caps, c);
#ifdef DEVICE_DEBUG
#ifdef GST_DEVICE_DEBUG
gchar *capstext = gst_structure_to_string (decice_cap_struct);
g_print("\nDevice caps: %s", capstext);
g_free(capstext);

View File

@@ -87,8 +87,8 @@ class Device
friend class DeviceSource;
Device();
Device(Rendering const& copy); // Not Implemented
Device& operator=(Rendering const& copy); // Not Implemented
Device(Device const& copy); // Not Implemented
Device& operator=(Device const& copy); // Not Implemented
public:
@@ -104,7 +104,7 @@ public:
std::string description (int index) const;
DeviceConfigSet config (int index) const;
int index (const std::string &device) const;
int index (const std::string &device) const;
bool exists (const std::string &device) const;
bool unplugged (const std::string &device) const;

View File

@@ -9,7 +9,7 @@
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] = { "720p", "1080p", "1440", "4K" };
const char* FrameBuffer::resolution_name[4] = { "720", "1080", "1440", "4K" };
float FrameBuffer::resolution_height[4] = { 720.f, 1080.f, 1440.f, 2160.f };
@@ -114,6 +114,24 @@ float FrameBuffer::aspectRatio() const
}
std::string FrameBuffer::info() const
{
std::string s = "";
static int num_ar = ((int)(sizeof(FrameBuffer::aspect_ratio_size) / sizeof(*FrameBuffer::aspect_ratio_size)));
float myratio = aspectRatio();
for(int i= 0; i < num_ar; i++) {
if ( myratio - (FrameBuffer::aspect_ratio_size[i].x / FrameBuffer::aspect_ratio_size[i].y ) < EPSILON)
{
s += std::string( FrameBuffer::aspect_ratio_name[i]) + ", ";
break;
}
}
s += std::to_string(width()) + "x" + std::to_string(height()) + " px";
return s;
}
glm::vec3 FrameBuffer::resolution() const
{
return glm::vec3(attrib_.viewport.x, attrib_.viewport.y, 0.f);

View File

@@ -40,6 +40,7 @@ public:
inline uint height() const { return attrib_.viewport.y; }
glm::vec3 resolution() const;
float aspectRatio() const;
std::string info() const;
// internal pixel format
inline bool use_alpha() const { return use_alpha_; }

60
FrameGrabber.h Normal file
View File

@@ -0,0 +1,60 @@
#ifndef FRAMEGRABBER_H
#define FRAMEGRABBER_H
#include <atomic>
#include <string>
#include <gst/gst.h>
#include "GlmToolkit.h"
class FrameBuffer;
/**
* @brief The FrameGrabber 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 FrameGrabber
{
uint64_t id_;
public:
FrameGrabber(): finished_(false), pbo_index_(0), pbo_next_index_(0), size_(0)
{
id_ = GlmToolkit::uniqueId();
pbo_[0] = pbo_[1] = 0;
}
virtual ~FrameGrabber() {}
inline uint64_t id() const { return id_; }
struct hasId: public std::unary_function<FrameGrabber*, bool>
{
inline bool operator()(const FrameGrabber* elem) const {
return (elem && elem->id() == _id);
}
hasId(uint64_t id) : _id(id) { }
private:
uint64_t _id;
};
virtual void addFrame(FrameBuffer *frame_buffer, float dt) = 0;
virtual void stop() { }
virtual std::string info() { return ""; }
virtual double duration() { return 0.0; }
virtual bool busy() { return false; }
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_;
};
#endif // FRAMEGRABBER_H

View File

@@ -33,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;

View File

@@ -20,6 +20,7 @@
#include "SessionSource.h"
#include "PatternSource.h"
#include "DeviceSource.h"
#include "NetworkSource.h"
#include "Settings.h"
#include "Mixer.h"
#include "ActionManager.h"
@@ -432,10 +433,9 @@ 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) );
}
@@ -444,6 +444,7 @@ void ImGuiVisitor::visit (SessionSource& s)
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();
@@ -532,3 +533,25 @@ void ImGuiVisitor::visit (DeviceSource& s)
}
}
void ImGuiVisitor::visit (NetworkSource& s)
{
ImGuiToolkit::Icon(s.icon().x, s.icon().y);
ImGui::SameLine(0, 10);
ImGui::Text("Network stream");
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_STREAM, 0.9f));
ImGui::Text("%s", s.connection().c_str());
ImGui::PopStyleColor(1);
NetworkStream *ns = s.networkStream();
ImGui::Text(" - %s (%dx%d)\n - Server address %s", NetworkToolkit::protocol_name[ns->protocol()],
ns->resolution().x, ns->resolution().y, ns->serverAddress().c_str());
if ( ImGui::Button( ICON_FA_REPLY " Reconnect", ImVec2(IMGUI_RIGHT_ALIGN, 0)) )
{
// TODO : reload ?
s.setConnection(s.connection());
}
}

View File

@@ -29,6 +29,7 @@ public:
void visit (CloneSource& s) override;
void visit (PatternSource& s) override;
void visit (DeviceSource& s) override;
void visit (NetworkSource& s) override;
};
#endif // IMGUIVISITOR_H

View File

@@ -75,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;
@@ -632,7 +632,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;

View File

@@ -25,7 +25,9 @@ using namespace tinyxml2;
#include "PatternSource.h"
#include "DeviceSource.h"
#include "StreamSource.h"
#include "NetworkSource.h"
#include "ActionManager.h"
#include "Streamer.h"
#include "Mixer.h"
@@ -44,12 +46,13 @@ 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");
@@ -111,7 +114,8 @@ 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.front_is_valid &&
Settings::application.recentSessions.filenames.size() > 0 )
Settings::application.recentSessions.filenames.size() > 0 &&
Settings::application.fresh_start)
load( Settings::application.recentSessions.filenames.front() );
else
// initializes with a new empty session
@@ -305,6 +309,18 @@ Source * Mixer::createSourceDevice(const std::string &namedevice)
}
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
@@ -800,7 +816,7 @@ void Mixer::swap()
back_session_ = tmp;
// swap recorders
back_session_->transferRecorders(session_);
back_session_->transferFrameGrabber(session_);
// attach new session's nodes to views
for (auto source_iter = session_->begin(); source_iter != session_->end(); source_iter++)
@@ -834,6 +850,9 @@ void Mixer::swap()
// reset History manager
Action::manager().clear();
// inform streaming manager
Streaming::manager().setSession(session_);
// notification
Log::Notify("Session %s loaded. %d source(s) created.", session_->filename().c_str(), session_->numSource());
}

View File

@@ -42,6 +42,7 @@ public:
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);

315
NetworkSource.cpp Normal file
View File

@@ -0,0 +1,315 @@
#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
received_config_ = false;
connected_ = false;
if (receiver_) {
delete receiver_;
receiver_ = nullptr;
close();
}
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() );
}
}
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::settings_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.");
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 icons
overlays_[View::MIXING]->attach( new Symbol(Symbol::SHARE, glm::vec3(0.8f, 0.8f, 0.01f)) );
overlays_[View::LAYER]->attach( 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("Creating Network Source '%s'", connection_name_.c_str());
// open network stream
networkStream()->connect( connection_name_ );
stream_->play(true);
}
std::string NetworkSource::connection() const
{
return connection_name_;
}
void NetworkSource::accept(Visitor& v)
{
Source::accept(v);
if (!failed())
v.visit(*this);
}

82
NetworkSource.h Normal file
View File

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

208
NetworkToolkit.cpp Normal file
View File

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

78
NetworkToolkit.h Normal file
View File

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

View File

@@ -137,13 +137,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);
}

View File

@@ -20,25 +20,13 @@
#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)
{
pbo_[0] = pbo_[1] = 0;
}
PNGRecorder::PNGRecorder() : Recorder()
PNGRecorder::PNGRecorder() : FrameGrabber()
{
std::string path = SystemToolkit::path_directory(Settings::application.record.path);
if (path.empty())
path = SystemToolkit::home_path();
filename_ = path + SystemToolkit::date_time_string() + "_vimix.png";
filename_ = path + "vimix_" + SystemToolkit::date_time_string() + ".png";
}
@@ -108,7 +96,8 @@ void PNGRecorder::addFrame(FrameBuffer *frame_buffer, float)
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
// ok done
glDeleteBuffers(2, pbo_);
if (pbo_[0] > 0)
glDeleteBuffers(2, pbo_);
// recorded one frame
finished_ = true;
@@ -123,7 +112,7 @@ void PNGRecorder::addFrame(FrameBuffer *frame_buffer, float)
}
const char* VideoRecorder::profile_name[VideoRecorder::DEFAULT] = {
"H264 (Baseline)",
"H264 (Realtime)",
"H264 (High 4:4:4)",
"H265 (Realtime)",
"H265 (HQ Animation)",
@@ -144,7 +133,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 +157,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,7 +189,7 @@ const std::vector<std::string> VideoRecorder::profile_description {
// "qtmux ! filesink name=sink";
VideoRecorder::VideoRecorder() : Recorder(), frame_buffer_(nullptr), width_(0), height_(0),
VideoRecorder::VideoRecorder() : FrameGrabber(), frame_buffer_(nullptr), width_(0), height_(0),
recording_(false), accept_buffer_(false), pipeline_(nullptr), src_(nullptr), timestamp_(0)
{
@@ -213,7 +207,8 @@ VideoRecorder::~VideoRecorder()
gst_object_unref (pipeline_);
}
glDeleteBuffers(2, pbo_);
if (pbo_[0] > 0)
glDeleteBuffers(2, pbo_);
}
void VideoRecorder::addFrame (FrameBuffer *frame_buffer, float dt)
@@ -243,7 +238,7 @@ void VideoRecorder::addFrame (FrameBuffer *frame_buffer, float dt)
glBufferData(GL_PIXEL_PACK_BUFFER, size_, NULL, GL_STREAM_READ);
// create a gstreamer pipeline
string description = "appsrc name=src ! videoconvert ! ";
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];
@@ -255,17 +250,17 @@ void VideoRecorder::addFrame (FrameBuffer *frame_buffer, float dt)
// setup filename & muxer
if( Settings::application.record.profile == JPEG_MULTI) {
std::string folder = path + SystemToolkit::date_time_string() + "_vimix_jpg";
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 + SystemToolkit::date_time_string() + "_vimix.webm";
filename_ = path + "vimix_" + SystemToolkit::date_time_string() + ".webm";
description += "webmmux ! filesink name=sink";
}
else {
filename_ = path + SystemToolkit::date_time_string() + "_vimix.mov";
filename_ = path + "vimix_" + SystemToolkit::date_time_string() + ".mov";
description += "qtmux ! filesink name=sink";
}
@@ -333,7 +328,7 @@ void VideoRecorder::addFrame (FrameBuffer *frame_buffer, float dt)
}
// all good
Log::Info("VideoRecorder start recording (%s %d x %d)", profile_name[Settings::application.record.profile], width_, height_);
Log::Info("VideoRecorder start (%s %d x %d)", profile_name[Settings::application.record.profile], width_, height_);
// start recording !!
recording_ = true;
@@ -409,6 +404,8 @@ void VideoRecorder::addFrame (FrameBuffer *frame_buffer, float dt)
gst_app_src_push_buffer (src_, buffer);
// NB: buffer will be unrefed by the appsrc
accept_buffer_ = false;
// next timestamp
timestamp_ += frame_duration_;
}
@@ -470,6 +467,11 @@ double VideoRecorder::duration()
return gst_guint64_to_gdouble( GST_TIME_AS_MSECONDS(timestamp_) ) / 1000.0;
}
bool VideoRecorder::busy()
{
return accept_buffer_ ? true : false;
}
// appsrc needs data and we should start sending
void VideoRecorder::callback_need_data (GstAppSrc *, guint , gpointer p)
{

View File

@@ -1,45 +1,14 @@
#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_;
@@ -51,7 +20,7 @@ public:
};
class VideoRecorder : public Recorder
class VideoRecorder : public FrameGrabber
{
std::string filename_;
@@ -96,9 +65,8 @@ public:
void addFrame(FrameBuffer *frame_buffer, float dt) override;
void stop() override;
std::string info() override;
double duration() override;
bool busy() override;
};

View File

@@ -84,12 +84,12 @@ static void WindowMoveCallback( GLFWwindow *w, int x, int 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();
}
}
@@ -223,7 +223,6 @@ void Rendering::pushBackDrawCallback(RenderingCallback function)
void Rendering::draw()
{
// operate on main window context
main_.makeCurrent();
@@ -254,6 +253,7 @@ void Rendering::draw()
glfwSwapBuffers(main_.window());
glfwSwapBuffers(output_.window());
// Poll and handle events (inputs, window resize, etc.)
// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application.
@@ -261,8 +261,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 +403,7 @@ WindowSurface::WindowSurface(Shader *s) : Primitive(s)
RenderingWindow::RenderingWindow() : window_(nullptr), master_(nullptr),
index_(-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)
{
}
@@ -433,8 +438,8 @@ void RenderingWindow::setIcon(const std::string &resource)
bool RenderingWindow::isFullscreen ()
{
return (glfwGetWindowMonitor(window_) != nullptr);
// return Settings::application.windows[index_].fullscreen;
// return (glfwGetWindowMonitor(window_) != nullptr);
return Settings::application.windows[index_].fullscreen;
}
GLFWmonitor *RenderingWindow::monitorAt(int x, int y)
@@ -505,41 +510,63 @@ GLFWmonitor *RenderingWindow::monitor()
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[index_].x,
Settings::application.windows[index_].y,
Settings::application.windows[index_].w,
Settings::application.windows[index_].h, 0 );
Settings::application.windows[index_].fullscreen = false;
}
// not in fullscreen mode
else {
// set to fullscreen mode
// 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);
}
}
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());
}
}
}
@@ -665,7 +692,7 @@ void RenderingWindow::show()
if ( Settings::application.windows[index_].fullscreen ) {
GLFWmonitor *mo = monitorNamed(Settings::application.windows[index_].monitor);
setFullscreen(mo);
setFullscreen_(mo);
}
}

View File

@@ -24,6 +24,8 @@ struct RenderingAttrib
class RenderingWindow
{
friend class Rendering;
GLFWwindow *window_, *master_;
RenderingAttrib window_attributes_;
int index_;
@@ -34,6 +36,9 @@ class RenderingWindow
uint fbo_;
class WindowSurface *surface_;
bool request_toggle_fullscreen_;
void toggleFullscreen_ ();
void setFullscreen_(GLFWmonitor *mo);
public:
RenderingWindow();
@@ -58,7 +63,7 @@ public:
// fullscreen
bool isFullscreen ();
void setFullscreen(GLFWmonitor *mo);
void exitFullscreen();
void toggleFullscreen ();
// get width of rendering area
@@ -122,7 +127,7 @@ public:
void popAttrib();
RenderingAttrib currentAttrib();
// get hold on the main window
// get hold on the windows
inline RenderingWindow& mainWindow() { return main_; }
inline RenderingWindow& outputWindow() { return output_; }

View File

@@ -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()

View File

@@ -5,7 +5,7 @@
#include "FrameBuffer.h"
#include "Session.h"
#include "GarbageVisitor.h"
#include "Recorder.h"
#include "FrameGrabber.h"
#include "SessionCreator.h"
#include "Log.h"
@@ -34,7 +34,7 @@ Session::Session() : failedSource_(nullptr), active_(true), fading_target_(0.f)
Session::~Session()
{
// delete all recorders
clearRecorders();
clearAllFrameGrabbers();
// delete all sources
for(auto it = sources_.begin(); it != sources_.end(); ) {
@@ -90,15 +90,15 @@ void Session::update(float dt)
render_.draw();
// send frame to recorders
std::list<Recorder *>::iterator iter;
for (iter=recorders_.begin(); iter != recorders_.end(); )
std::list<FrameGrabber *>::iterator iter;
for (iter=grabbers_.begin(); iter != grabbers_.end(); )
{
Recorder *rec = *iter;
FrameGrabber *rec = *iter;
rec->addFrame(render_.frame(), dt);
if (rec->finished()) {
iter = recorders_.erase(iter);
iter = grabbers_.erase(iter);
delete rec;
}
else {
@@ -295,49 +295,62 @@ int Session::index(SourceList::iterator it) const
return index;
}
void Session::addRecorder(Recorder *rec)
void Session::addFrameGrabber(FrameGrabber *rec)
{
recorders_.push_back(rec);
if (rec != nullptr)
grabbers_.push_back(rec);
}
Recorder *Session::frontRecorder()
FrameGrabber *Session::frontFrameGrabber()
{
if (recorders_.empty())
if (grabbers_.empty())
return nullptr;
else
return recorders_.front();
return grabbers_.front();
}
void Session::stopRecorders()
FrameGrabber *Session::getFrameGrabber(uint64_t id)
{
std::list<Recorder *>::iterator iter;
for (iter=recorders_.begin(); iter != recorders_.end(); )
if (id > 0 && grabbers_.size() > 0 )
{
std::list<FrameGrabber *>::iterator iter = std::find_if(grabbers_.begin(), grabbers_.end(), FrameGrabber::hasId(id));
if (iter != grabbers_.end())
return (*iter);
}
return nullptr;
}
void Session::stopAllFrameGrabbers()
{
std::list<FrameGrabber *>::iterator iter;
for (iter=grabbers_.begin(); iter != grabbers_.end(); )
(*iter)->stop();
}
void Session::clearRecorders()
void Session::clearAllFrameGrabbers()
{
std::list<Recorder *>::iterator iter;
for (iter=recorders_.begin(); iter != recorders_.end(); )
std::list<FrameGrabber *>::iterator iter;
for (iter=grabbers_.begin(); iter != grabbers_.end(); )
{
Recorder *rec = *iter;
FrameGrabber *rec = *iter;
rec->stop();
iter = recorders_.erase(iter);
iter = grabbers_.erase(iter);
delete rec;
}
}
void Session::transferRecorders(Session *dest)
void Session::transferFrameGrabber(Session *dest)
{
if (dest == nullptr)
return;
std::list<Recorder *>::iterator iter;
for (iter=recorders_.begin(); iter != recorders_.end(); )
std::list<FrameGrabber *>::iterator iter;
for (iter=grabbers_.begin(); iter != grabbers_.end(); )
{
dest->recorders_.push_back(*iter);
iter = recorders_.erase(iter);
dest->grabbers_.push_back(*iter);
iter = grabbers_.erase(iter);
}
}

View File

@@ -6,7 +6,7 @@
#include "View.h"
#include "Source.h"
class Recorder;
class FrameGrabber;
class Session
{
@@ -59,11 +59,12 @@ public:
inline FrameBuffer *frame () const { return render_.frame(); }
// Recorders
void addRecorder(Recorder *rec);
Recorder *frontRecorder();
void stopRecorders();
void clearRecorders();
void transferRecorders(Session *dest);
void addFrameGrabber(FrameGrabber *rec);
FrameGrabber *frontFrameGrabber();
FrameGrabber *getFrameGrabber(uint64_t id);
void stopAllFrameGrabbers();
void clearAllFrameGrabbers();
void transferFrameGrabber(Session *dest);
// configure rendering resolution
void setResolution(glm::vec3 resolution);
@@ -90,7 +91,7 @@ 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_;
};

View File

@@ -11,6 +11,7 @@
#include "StreamSource.h"
#include "PatternSource.h"
#include "DeviceSource.h"
#include "NetworkSource.h"
#include "Session.h"
#include "ImageShader.h"
#include "ImageProcessingShader.h"
@@ -36,9 +37,16 @@ std::string SessionCreator::info(const std::string& filename)
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;
@@ -187,6 +195,9 @@ void SessionLoader::load(XMLElement *sessionNode)
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)
@@ -527,4 +538,14 @@ void SessionLoader::visit (DeviceSource& s)
}
void SessionLoader::visit (NetworkSource& s)
{
std::string connect = std::string ( xmlCurrent_->Attribute("connection") );
// change only if different device
if ( connect != s.connection() )
s.setConnection(connect);
}

View File

@@ -49,6 +49,7 @@ public:
void visit (SessionSource& s) override;
void visit (PatternSource& s) override;
void visit (DeviceSource& s) override;
void visit (NetworkSource& s) override;
protected:
tinyxml2::XMLElement *xmlCurrent_;

View File

@@ -8,6 +8,7 @@
#include "SessionSource.h"
#include "PatternSource.h"
#include "DeviceSource.h"
#include "NetworkSource.h"
#include "ImageShader.h"
#include "ImageProcessingShader.h"
#include "MediaPlayer.h"
@@ -112,7 +113,7 @@ void SessionVisitor::visit(Primitive &n)
}
void SessionVisitor::visit(Surface &n)
void SessionVisitor::visit(Surface &)
{
}
@@ -128,7 +129,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");
@@ -269,7 +270,7 @@ void SessionVisitor::visit(LineSquare &)
}
void SessionVisitor::visit(LineCircle &n)
void SessionVisitor::visit(LineCircle &)
{
// Node of a different type
xmlCurrent_->SetAttribute("type", "LineCircle");
@@ -372,7 +373,7 @@ void SessionVisitor::visit (SessionSource& s)
path->InsertEndChild( text );
}
void SessionVisitor::visit (RenderSource& s)
void SessionVisitor::visit (RenderSource&)
{
xmlCurrent_->SetAttribute("type", "RenderSource");
}
@@ -402,3 +403,9 @@ void SessionVisitor::visit (DeviceSource& s)
xmlCurrent_->SetAttribute("type", "DeviceSource");
xmlCurrent_->SetAttribute("device", s.device().c_str() );
}
void SessionVisitor::visit (NetworkSource& s)
{
xmlCurrent_->SetAttribute("type", "NetworkSource");
xmlCurrent_->SetAttribute("connection", s.connection().c_str() );
}

View File

@@ -24,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;
@@ -44,10 +44,11 @@ public:
void visit (Source& s) override;
void visit (MediaSource& s) override;
void visit (SessionSource& s) override;
void visit (RenderSource& s) override;
void visit (RenderSource&) override;
void visit (CloneSource& s) override;
void visit (PatternSource& s) override;
void visit (DeviceSource& s) override;
void visit (NetworkSource& s) override;
static tinyxml2::XMLElement *NodeToXML(Node &n, tinyxml2::XMLDocument *doc);
};

View File

@@ -12,6 +12,7 @@ using namespace tinyxml2;
Settings::Application Settings::application;
static string settingsFilename = "";
void Settings::Save()
@@ -58,6 +59,8 @@ 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
@@ -104,6 +107,21 @@ void Settings::Save()
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" );
@@ -182,6 +200,7 @@ void Settings::Save()
XMLError eResult = xmlDoc.SaveFile(settingsFilename.c_str());
XMLResultError(eResult);
}
void Settings::Load()
@@ -212,6 +231,8 @@ 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
@@ -320,6 +341,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");
@@ -346,7 +383,7 @@ void Settings::Load()
pSession->QueryBoolAttribute("autosave", &application.recentSessions.save_on_exit);
pSession->QueryBoolAttribute("valid", &application.recentSessions.front_is_valid);
}
// recent session filenames
// recent session folders
XMLElement * pFolder = pElement->FirstChildElement("Folder");
if (pFolder)
{
@@ -380,7 +417,42 @@ 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) {
fscanf(file, "%d", &l);
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);
}
}
@@ -395,3 +467,4 @@ void Settings::Check()
xmlDoc.Print();
}

View File

@@ -161,6 +161,10 @@ struct SourceConfig
struct Application
{
// instance check
bool fresh_start;
int instance_id;
// Verification
std::string name;
std::string executable;
@@ -173,6 +177,10 @@ struct Application
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;
@@ -183,7 +191,7 @@ struct Application
// settings render
RenderConfig render;
// settings render
// settings exporters
RecordConfig record;
// settings new source
@@ -200,13 +208,14 @@ struct Application
History recentFolders;
History recentImport;
Application() : name(APP_NAME){
Application() : fresh_start(false), 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;
@@ -225,6 +234,8 @@ extern Application application;
// Save and Load store settings in XML file
void Save();
void Load();
void Lock();
void Unlock();
void Check();
}

View File

@@ -97,7 +97,7 @@ void Stream::execute_open()
// reset
ready_ = false;
// Create the gstreamer pipeline possible :
// Add custom app sink to the gstreamer pipeline
string description = description_;
description += " ! appsink name=sink";
@@ -134,7 +134,7 @@ void Stream::execute_open()
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), 50);
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_
@@ -259,7 +259,7 @@ float Stream::aspectRatio() const
void Stream::enable(bool on)
{
if ( !ready_ )
if ( !ready_ || pipeline_ == nullptr)
return;
if ( enabled_ != on ) {
@@ -333,10 +333,8 @@ void Stream::play(bool on)
#endif
// activate live-source
if (live_) {
GstState state;
gst_element_get_state (pipeline_, &state, NULL, GST_CLOCK_TIME_NONE);
}
if (live_)
gst_element_get_state (pipeline_, NULL, NULL, GST_CLOCK_TIME_NONE);
// reset time counter
timecount_.reset();
@@ -392,7 +390,7 @@ void Stream::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;
@@ -482,7 +480,6 @@ void Stream::update()
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) {
@@ -496,11 +493,9 @@ void Stream::update()
// 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 )
{
@@ -520,12 +515,15 @@ void Stream::update()
// 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
@@ -630,14 +628,13 @@ GstFlowReturn Stream::callback_new_preroll (GstAppSink *sink, gpointer p)
// if got a valid sample
if (sample != NULL) {
// get buffer from sample
GstBuffer *buf = gst_sample_get_buffer (sample);
// 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;
@@ -656,7 +653,8 @@ GstFlowReturn Stream::callback_new_sample (GstAppSink *sink, gpointer p)
{
GstFlowReturn ret = GST_FLOW_OK;
// Log::Info("callback_new_sample");
// 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);
@@ -664,12 +662,13 @@ GstFlowReturn Stream::callback_new_sample (GstAppSink *sink, gpointer p)
// if got a valid sample
if (sample != NULL && !gst_app_sink_is_eos (sink)) {
// get buffer from sample (valid until sample is released)
GstBuffer *buf = gst_sample_get_buffer (sample) ;
// 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;

596
Streamer.cpp Normal file
View File

@@ -0,0 +1,596 @@
#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), session_(nullptr), width_(0), height_(0)
{
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::setSession(Session *se)
{
if (se != nullptr && session_ != se) {
session_ = se;
FrameBuffer *f = session_->frame();
width_ = f->width();
height_ = f->height();
}
else {
session_ = nullptr;
width_ = 0;
height_ = 0;
}
}
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 = width_;
conf.height = height_;
// 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_.push_back(streamer);
// start streamer
session_->addFrameGrabber(streamer);
}
VideoStreamer::VideoStreamer(NetworkToolkit::StreamConfig conf): FrameGrabber(), frame_buffer_(nullptr), width_(0), height_(0),
streaming_(false), accept_buffer_(false), pipeline_(nullptr), src_(nullptr), timestamp_(0)
{
// configure fix parameter
frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, 30); // 30 FPS
timeframe_ = 2 * frame_duration_;
config_ = conf;
}
VideoStreamer::~VideoStreamer()
{
stop();
if (src_ != nullptr)
gst_object_unref (src_);
if (pipeline_ != nullptr) {
gst_element_set_state (pipeline_, GST_STATE_NULL);
gst_object_unref (pipeline_);
}
if (pbo_[0] > 0)
glDeleteBuffers(2, pbo_);
}
void VideoStreamer::addFrame (FrameBuffer *frame_buffer, float dt)
{
// ignore
if (frame_buffer == nullptr)
return;
// first frame for initialization
if (frame_buffer_ == nullptr) {
// set frame buffer as input
frame_buffer_ = frame_buffer;
// define frames properties
width_ = frame_buffer_->width();
height_ = frame_buffer_->height();
size_ = width_ * height_ * (frame_buffer_->use_alpha() ? 4 : 3);
// if an incompatilble frame buffer given: cancel streaming
if ( config_.width != width_ || config_.height != height_) {
Log::Warning("Streaming cannot start: given frames (%d x %d) incompatible with stream (%d x %d)",
width_, height_, config_.width, config_.height);
finished_ = true;
return;
}
// 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);
// 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) {
// TODO rename SHM socket "shm_PORT"
std::string path = SystemToolkit::full_filename(SystemToolkit::settings_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,
NULL);
// Direct encoding (no buffering)
gst_app_src_set_max_bytes( src_, 0 );
// 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("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::Info("Streaming video to %s (%d x %d)",
config_.client_name.c_str(), width_, height_);
// start streaming !!
streaming_ = 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("Streaming 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 (streaming_ && 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
gst_app_src_push_buffer (src_, buffer);
// NB: buffer will be unrefed by the appsrc
accept_buffer_ = false;
// 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 streaming receive 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(4));
if (msg) {
// stop the pipeline
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_NULL);
#ifdef STREAMER_DEBUG
if (ret == GST_STATE_CHANGE_FAILURE)
Log::Info("Streaming to %s:%d could not stop properly.", config_.client_address.c_str(), config_.port);
else
Log::Info("Streaming to %s:%d ending...", config_.client_address.c_str(), config_.port);
#endif
finished_ = true;
}
}
// finished !
else {
// 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::settings_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
streaming_ = false;
finished_ = true;
}
std::string VideoStreamer::info()
{
std::ostringstream ret;
if (streaming_) {
ret << NetworkToolkit::protocol_name[config_.protocol];
ret << " to ";
ret << config_.client_name;
}
else
ret << "Streaming terminated.";
return ret.str();
}
double VideoStreamer::duration()
{
return gst_guint64_to_gdouble( GST_TIME_AS_MSECONDS(timestamp_) ) / 1000.0;
}
bool VideoStreamer::busy()
{
return accept_buffer_ ? true : false;
}
// appsrc needs data and we should start sending
void VideoStreamer::callback_need_data (GstAppSrc *, guint , gpointer p)
{
VideoStreamer *rec = (VideoStreamer *)p;
if (rec) {
rec->accept_buffer_ = true;
// Log::Info("VideoStreamer need_data");
}
}
// appsrc has enough data and we can stop sending
void VideoStreamer::callback_enough_data (GstAppSrc *, gpointer p)
{
// Log::Info("VideoStreamer enough_data");
VideoStreamer *rec = (VideoStreamer *)p;
if (rec) {
rec->accept_buffer_ = false;
}
}

110
Streamer.h Normal file
View File

@@ -0,0 +1,110 @@
#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 setSession(Session *se);
void removeStreams(const std::string &clientname);
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);
void removeStream(const std::string &sender, int port);
private:
bool enabled_;
StreamingRequestListener listener_;
UdpListeningReceiveSocket *receiver_;
Session *session_;
int width_;
int height_;
std::vector<VideoStreamer *> streamers_;
std::mutex streamers_lock_;
};
class VideoStreamer : public FrameGrabber
{
friend class Streaming;
// Frame buffer information
FrameBuffer *frame_buffer_;
uint width_;
uint height_;
// connection information
NetworkToolkit::StreamConfig config_;
// operation
std::atomic<bool> streaming_;
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);
public:
VideoStreamer(NetworkToolkit::StreamConfig conf);
~VideoStreamer();
void addFrame(FrameBuffer *frame_buffer, float dt) override;
void stop() override;
std::string info() override;
double duration() override;
bool busy() override;
};
#endif // STREAMER_H

View File

@@ -214,6 +214,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
@@ -314,5 +325,19 @@ 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();;

View File

@@ -54,11 +54,16 @@ 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();

View File

@@ -39,6 +39,7 @@ using namespace std;
#include "Log.h"
#include "SystemToolkit.h"
#include "RenderingManager.h"
#include "Connection.h"
#include "ActionManager.h"
#include "Resource.h"
#include "FileDialog.h"
@@ -50,12 +51,14 @@ using namespace std;
#include "GstToolkit.h"
#include "Mixer.h"
#include "Recorder.h"
#include "Streamer.h"
#include "Selection.h"
#include "FrameBuffer.h"
#include "MediaPlayer.h"
#include "MediaSource.h"
#include "PatternSource.h"
#include "DeviceSource.h"
#include "NetworkSource.h"
#include "StreamSource.h"
#include "PickingVisitor.h"
#include "ImageShader.h"
@@ -151,12 +154,18 @@ static std::string FolderDialog(const std::string &path)
UserInterface::UserInterface()
{
ctrl_modifier_active = false;
alt_modifier_active = false;
shift_modifier_active = false;
show_vimix_config = false;
show_imgui_about = false;
show_gst_about = false;
show_opengl_about = false;
currentTextEdit = "";
screenshot_step = 0;
video_recorder_ = 0;
// video_streamer_ = 0;
}
bool UserInterface::Init()
@@ -294,11 +303,16 @@ void UserInterface::handleKeyboard()
}
else if (ImGui::IsKeyPressed( GLFW_KEY_R )) {
// toggle recording
Recorder *rec = Mixer::manager().session()->frontRecorder();
if (rec)
FrameGrabber *rec = Mixer::manager().session()->getFrameGrabber(video_recorder_);
if (rec) {
rec->stop();
else
Mixer::manager().session()->addRecorder(new VideoRecorder);
video_recorder_ = 0;
}
else {
FrameGrabber *fg = new VideoRecorder;
video_recorder_ = fg->id();
Mixer::manager().session()->addFrameGrabber(fg);
}
}
else if (ImGui::IsKeyPressed( GLFW_KEY_Z )) {
if (shift_modifier_active)
@@ -323,6 +337,9 @@ void UserInterface::handleKeyboard()
if (clipboard != nullptr && strlen(clipboard) > 0)
Mixer::manager().paste(clipboard);
}
else if (ImGui::IsKeyPressed( GLFW_KEY_F ) && shift_modifier_active) {
Rendering::manager().mainWindow().toggleFullscreen();
}
}
// No CTRL modifier
else {
@@ -335,8 +352,6 @@ void UserInterface::handleKeyboard()
Mixer::manager().setView(View::GEOMETRY);
else if (ImGui::IsKeyPressed( GLFW_KEY_F3 ))
Mixer::manager().setView(View::LAYER);
else if (ImGui::IsKeyPressed( GLFW_KEY_F11 ))
Rendering::manager().mainWindow().toggleFullscreen();
else if (ImGui::IsKeyPressed( GLFW_KEY_F12 ))
StartScreenshot();
// normal keys // make sure no entry / window box is active
@@ -347,7 +362,7 @@ void UserInterface::handleKeyboard()
// button esc to toggle fullscreen
else if (ImGui::IsKeyPressed( GLFW_KEY_ESCAPE )) {
if (Rendering::manager().mainWindow().isFullscreen())
Rendering::manager().mainWindow().setFullscreen(nullptr);
Rendering::manager().mainWindow().exitFullscreen();
else if (navigator.pannelVisible())
navigator.hidePannel();
else if (!Mixer::selection().empty()) {
@@ -773,10 +788,11 @@ void UserInterface::Render()
if (Settings::application.widget.stats)
ImGuiToolkit::ShowStats(&Settings::application.widget.stats, &Settings::application.widget.stats_corner);
// TODO: better management of main_video_recorder
Recorder *rec = Mixer::manager().session()->frontRecorder();
// management of video_recorder
FrameGrabber *rec = Mixer::manager().session()->getFrameGrabber(video_recorder_);
if (rec && rec->duration() > Settings::application.record.timeout ){
rec->stop();
video_recorder_ = 0;
}
// all IMGUI Rendering
@@ -1089,7 +1105,7 @@ void UserInterface::RenderPreview()
}
Recorder *rec = Mixer::manager().session()->frontRecorder();
FrameGrabber *rec = Mixer::manager().session()->getFrameGrabber(video_recorder_);
// return from thread for folder openning
if ( !recordFolderFileDialogs.empty() ) {
@@ -1121,25 +1137,35 @@ void UserInterface::RenderPreview()
}
if (ImGui::BeginMenu("Record"))
{
if ( ImGui::MenuItem( ICON_FA_CAMERA_RETRO " Capture frame (PNG)") )
Mixer::manager().session()->addRecorder(new PNGRecorder);
// Stop recording menu if main recorder already exists
if (rec) {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_RECORD, 0.8f));
if ( ImGui::MenuItem( ICON_FA_SQUARE " Stop Record", CTRL_MOD "R") ) {
rec->stop();
video_recorder_ = 0;
}
ImGui::PopStyleColor(1);
}
// start recording
else {
// detecting the absence of video recorder but the variable is still not 0: fix this!
if (video_recorder_ > 0)
video_recorder_ = 0;
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_RECORD, 0.9f));
if ( ImGui::MenuItem( ICON_FA_CIRCLE " Record", CTRL_MOD "R") ) {
Mixer::manager().session()->addRecorder(new VideoRecorder);
FrameGrabber *fg = new VideoRecorder;
video_recorder_ = fg->id();
Mixer::manager().session()->addFrameGrabber(fg);
}
ImGui::PopStyleColor(1);
// select profile
ImGui::SetNextItemWidth(300);
ImGui::Combo("##RecProfile", &Settings::application.record.profile, VideoRecorder::profile_name, IM_ARRAYSIZE(VideoRecorder::profile_name) );
}
if ( ImGui::MenuItem( ICON_FA_CAMERA_RETRO " Capture frame (PNG)") )
Mixer::manager().session()->addFrameGrabber(new PNGRecorder);
// Options menu
ImGui::Separator();
ImGui::MenuItem("Options", nullptr, false, false);
@@ -1177,6 +1203,30 @@ void UserInterface::RenderPreview()
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Stream"))
{
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_STREAM, 0.9f));
if ( ImGui::MenuItem( ICON_FA_SHARE_ALT " Accept connections", NULL, &Settings::application.accept_connections) ) {
Streaming::manager().enable(Settings::application.accept_connections);
}
ImGui::PopStyleColor(1);
if (Settings::application.accept_connections)
{
static char dummy_str[512];
sprintf(dummy_str, "%s", Connection::manager().info().name.c_str());
ImGui::InputText("My network ID", dummy_str, IM_ARRAYSIZE(dummy_str), ImGuiInputTextFlags_ReadOnly);
std::vector<std::string> ls = Streaming::manager().listStreams();
if (ls.size()>0) {
ImGui::Separator();
ImGui::MenuItem("Active streams", nullptr, false, false);
for (auto it = ls.begin(); it != ls.end(); it++)
ImGui::Text(" %s", (*it).c_str() );
}
}
ImGui::EndMenu();
}
ImGui::EndMenuBar();
}
@@ -1188,17 +1238,6 @@ void UserInterface::RenderPreview()
ImVec2 draw_pos = ImGui::GetCursorScreenPos();
// preview image
ImGui::Image((void*)(intptr_t)output->texture(), imagesize);
// recording indicator overlay
if (rec)
{
float r = ImGui::GetTextLineHeightWithSpacing();
ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + r, draw_pos.y + r));
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0, 0.05, 0.05, 0.8f));
ImGui::Text(ICON_FA_CIRCLE " %s", rec->info().c_str() );
ImGui::PopStyleColor(1);
ImGui::PopFont();
}
// tooltip overlay
if (ImGui::IsItemHovered())
{
@@ -1207,6 +1246,31 @@ void UserInterface::RenderPreview()
ImGui::SetCursorScreenPos(draw_pos);
ImGui::Text(" %d x %d px, %d fps", output->width(), output->height(), int(1000.f / Mixer::manager().dt()) );
}
// recording indicator overlay
if (rec)
{
float r = ImGui::GetTextLineHeightWithSpacing();
ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + r, draw_pos.y + r));
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_RECORD, 0.8f));
ImGui::Text(ICON_FA_CIRCLE " %s", rec->info().c_str() );
ImGui::PopStyleColor(1);
ImGui::PopFont();
}
// streaming indicator overlay
if (Settings::application.accept_connections)
{
float r = ImGui::GetTextLineHeightWithSpacing();
ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + width - 2.f * r, draw_pos.y + r));
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
if ( Streaming::manager().busy())
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_STREAM, 0.8f));
else
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_STREAM, 0.2f));
ImGui::Text(ICON_FA_SHARE_ALT_SQUARE);
ImGui::PopStyleColor(1);
ImGui::PopFont();
}
ImGui::End();
}
@@ -2054,10 +2118,10 @@ void Navigator::RenderNewPannel()
static const char* origin_names[4] = { ICON_FA_PHOTO_VIDEO " File",
ICON_FA_SYNC " Internal",
ICON_FA_COG " Generated",
ICON_FA_PLUG " External"
ICON_FA_PLUG " Connected"
};
// TODO IMPLEMENT EXTERNAL SOURCES static const char* origin_names[3] = { ICON_FA_FILE " File", ICON_FA_SITEMAP " Internal", ICON_FA_PLUG " External" };
if (ImGui::Combo("Origin", &Settings::application.source.new_type, origin_names, IM_ARRAYSIZE(origin_names)) )
if (ImGui::Combo("##Origin", &Settings::application.source.new_type, origin_names, IM_ARRAYSIZE(origin_names)) )
new_source_preview_.setSource();
// File Source creation
@@ -2076,7 +2140,7 @@ void Navigator::RenderNewPannel()
// Indication
ImGui::SameLine();
ImGuiToolkit::HelpMarker("Create a source from a file:\n- Video (*.mpg, *mov, *.avi, etc.)\n- Image (*.jpg, *.png, etc.)\n- Vector graphics (*.svg)\n- vimix session (*.mix)\n\nEquivalent to dropping the file in the workspace.");
ImGuiToolkit::HelpMarker("Create a source from a file:\n- video (*.mpg, *mov, *.avi, etc.)\n- image (*.jpg, *.png, etc.)\n- vector graphics (*.svg)\n- vimix session (*.mix)\n\n(Equivalent to dropping the file in the workspace)");
// if a file dialog future was registered
if ( !fileImportFileDialogs.empty() ) {
@@ -2184,14 +2248,13 @@ void Navigator::RenderNewPannel()
Pattern::pattern_types[pattern_type]);
}
}
// Hardware
// External source creator
else if (Settings::application.source.new_type == 3){
ImGui::SetCursorPosY(2.f * width_);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
if (ImGui::BeginCombo("##Hardware", "Select device"))
if (ImGui::BeginCombo("##External", "Select device"))
{
for (int d = 0; d < Device::manager().numDevices(); ++d){
std::string namedev = Device::manager().name(d);
@@ -2200,12 +2263,18 @@ void Navigator::RenderNewPannel()
new_source_preview_.setSource( Mixer::manager().createSourceDevice(namedev), namedev);
}
}
for (int d = 1; d < Connection::manager().numHosts(); ++d){
std::string namehost = Connection::manager().info(d).name;
if (ImGui::Selectable( namehost.c_str() )) {
new_source_preview_.setSource( Mixer::manager().createSourceNetwork(namehost), namehost);
}
}
ImGui::EndCombo();
}
// Indication
ImGui::SameLine();
ImGuiToolkit::HelpMarker("Create a source with images from external devices or network.");
ImGuiToolkit::HelpMarker("Create a source getting images from connected devices or machines;\n- webcams or frame grabbers\n- screen capture\n- vimix stream from connected machines");
}
@@ -2294,18 +2363,13 @@ void Navigator::RenderMainPannel()
ImGui::Text(APP_NAME);
ImGui::PopFont();
// TODO fixe fullscreen for OSX :(
#ifndef APPLE
// Icon to switch fullscreen
ImGui::SetCursorPos(ImVec2(pannel_width_ - 35.f, 15.f));
const char *tooltip[2] = {"Enter Fullscreen", "Exit Fullscreen"};
const char *tooltip[2] = {"Enter Fullscreen (" CTRL_MOD "Shift+F)", "Exit Fullscreen (" CTRL_MOD "Shift+F)"};
bool fs = Rendering::manager().mainWindow().isFullscreen();
if ( ImGuiToolkit::IconToggle(3,15,2,15, &fs, tooltip ) ) {
Rendering::manager().mainWindow().toggleFullscreen();
ImGui::End();
return;
}
#endif
// Session menu
ImGui::SetCursorPosY(width_);
ImGui::Text("Session");

View File

@@ -115,6 +115,10 @@ class UserInterface
bool show_opengl_about;
unsigned int screenshot_step;
// frame grabbers
uint64_t video_recorder_;
// uint64_t video_streamer_;
// Private Constructor
UserInterface();
UserInterface(UserInterface const& copy); // Not Implemented

View File

@@ -33,6 +33,7 @@ class GenericStreamSource;
class SessionSource;
class RenderSource;
class CloneSource;
class NetworkSource;
// Declares the interface for the visitors
class Visitor {
@@ -66,6 +67,7 @@ public:
// 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&) {}

View File

@@ -50,9 +50,11 @@
#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+"
@@ -71,5 +73,9 @@
#define COLOR_SLIDER_CIRCLE 0.11f, 0.11f, 0.11f
#define COLOR_STASH_CIRCLE 0.06f, 0.06f, 0.06f
// use glReadPixel or glGetTextImage
// read pixels & pbo should be the fastest
// https://stackoverflow.com/questions/38140527/glreadpixels-vs-glgetteximage
#define USE_GLREADPIXEL
#endif // VMIX_DEFINES_H

34
ext/OSCPack/LICENSE Normal file
View File

@@ -0,0 +1,34 @@
oscpack -- Open Sound Control (OSC) packet manipulation library
http://www.rossbencina.com/code/oscpack
Copyright (c) 2004-2013 Ross Bencina <rossb@audiomulch.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
###
The text above constitutes the entire oscpack license; however,
the oscpack developer(s) also make the following non-binding requests:
Any person wishing to distribute modifications to the Software is
requested to send the modifications to the original developer so that
they can be incorporated into the canonical version. It is also
requested that these non-binding requests be included whenever the
above license is reproduced.

150
ext/OSCPack/README Normal file
View File

@@ -0,0 +1,150 @@
oscpack -- Open Sound Control packet manipulation library
A simple C++ library for packing and unpacking OSC packets.
http://www.rossbencina.com/code/oscpack
Copyright (c) 2004-2013 Ross Bencina <rossb@audiomulch.com>
Oscpack is simply a set of C++ classes for packing and unpacking OSC packets.
Oscpack includes a minimal set of UDP networking classes for Windows and POSIX.
The networking classes are sufficient for writing many OSC applications and servers,
but you are encouraged to use another networking framework if it better suits your needs.
Oscpack is not an OSC application framework. It doesn't include infrastructure for
constructing or routing OSC namespaces, just classes for easily constructing,
sending, receiving and parsing OSC packets. The library should also be easy to use
for other transport methods (e.g. serial).
The key goals of the oscpack library are:
- Be a simple and complete implementation of OSC
- Be portable to a wide variety of platforms
- Allow easy development of robust OSC applications
(for example it should be impossible to crash a server
by sending it malformed packets, and difficult to create
malformed packets.)
Here's a quick run down of the key files:
osc/OscReceivedElements -- classes for parsing a packet
osc/OscPrintRecievedElements -- iostream << operators for printing packet elements
osc/OscOutboundPacketStream -- a class for packing messages into a packet
osc/OscPacketListener -- base class for listening to OSC packets on a UdpSocket
ip/IpEndpointName -- class that represents an IP address and port number
ip/UdpSocket -- classes for UDP transmission and listening sockets
tests/OscUnitTests -- unit test program for the OSC modules
tests/OscSendTests -- examples of how to send messages
tests/OscReceiveTest -- example of how to receive the messages sent by OSCSendTests
examples/OscDump -- a program that prints received OSC packets
examples/SimpleSend -- a minimal program to send an OSC message
examples/SimpleReceive -- a minimal program to receive an OSC message
osc/ contains all of the OSC related classes
ip/ contains the networking classes
ip/windows contains the Windows implementation of the networking classes
ip/posix contains the POSIX implementation of the networking classes
Building
--------
The idea is that you will embed this source code in your projects as you
see fit. The Makefile has an install rule for building a shared library and
installing headers in usr/local. It can also build a static library.
There is a CMakeLists.txt for building with cmake.
Makefile builds
...............
The Makefile works for Linux and Max OS X. It should also work on other platforms
that have make. Just run:
$ make
You can run "make install" if you like.
Cmake builds
............
There is a CMakeLists.txt file which has been tested with cmake on
Windows and Linux. It should work on other platforms too.
For example, to generate a Visual Studio 10 project, run cmake
like this:
> cmake -G "Visual Studio 10"
Run cmake without any parameters to get a list of available generators.
Mingw build batch file
......................
For Windows there is a batch file for doing a simple test build with
MinGW gcc called make.MinGW32.bat. This will build the test executables
and oscdump in ./bin and run the unit tests.
Note:
In some rare instances you may need to edit the Makefile or
osc/OscHostEndianness.h to configure oscpack for the endianness of your
processor (see the comments at the top of the Makefile for details).
Verification test
-----------------
To run the unit tests:
$ ./bin/OscUnitTests
To run the send and receive tests. Open two terminals. In one run:
$ ./bin/OscReceiveTest
Then in the other terminal run:
$./bin/OscSendTests
You should see an indication that the messages were received
in the first terminal.
Note that OscSendTests intentionally sends some unexpected
message parameters to test exception handling in the receiver.
You will see some "error while parsing message" messages printed.
You can use ./bin/OscDump to print out OSC messages received
from any program, including the test programs.
--
If you fix anything or write a set of TCP send/receive classes
please consider sending me a patch. My email address is
rossb@audiomulch.com. Thanks :)
For more information about Open Sound Control, see:
http://opensoundcontrol.org/
Thanks to Till Bovermann for helping with POSIX networking code and
Mac compatibility, and to Martin Kaltenbrunner and the rest of the
reacTable team for giving me a reason to finish this library. Thanks
to Merlijn Blaauw for reviewing the interfaces. Thanks to Xavier Oliver
for additional help with Linux builds and POSIX implementation details.
Portions developed at the Music Technology Group, Audiovisual Institute,
University Pompeu Fabra, Barcelona, during my stay as a visiting
researcher, November 2004 - September 2005.
Thanks to Syneme at the University of Calgary for providing financial
support for the 1.1.0 update, December 2012 - March 2013.
See the file CHANGES for information about recent updates.
See the file LICENSE for information about distributing and using this code.
###

View File

@@ -0,0 +1,88 @@
/*
oscpack -- Open Sound Control (OSC) packet manipulation library
http://www.rossbencina.com/code/oscpack
Copyright (c) 2004-2013 Ross Bencina <rossb@audiomulch.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
The text above constitutes the entire oscpack license; however,
the oscpack developer(s) also make the following non-binding requests:
Any person wishing to distribute modifications to the Software is
requested to send the modifications to the original developer so that
they can be incorporated into the canonical version. It is also
requested that these non-binding requests be included whenever the
above license is reproduced.
*/
#include "IpEndpointName.h"
#include <cstdio>
#include "NetworkingUtils.h"
unsigned long IpEndpointName::GetHostByName( const char *s )
{
return ::GetHostByName(s);
}
void IpEndpointName::AddressAsString( char *s ) const
{
if( address == ANY_ADDRESS ){
std::sprintf( s, "<any>" );
}else{
std::sprintf( s, "%d.%d.%d.%d",
(int)((address >> 24) & 0xFF),
(int)((address >> 16) & 0xFF),
(int)((address >> 8) & 0xFF),
(int)(address & 0xFF) );
}
}
void IpEndpointName::AddressAndPortAsString( char *s ) const
{
if( port == ANY_PORT ){
if( address == ANY_ADDRESS ){
std::sprintf( s, "<any>:<any>" );
}else{
std::sprintf( s, "%d.%d.%d.%d:<any>",
(int)((address >> 24) & 0xFF),
(int)((address >> 16) & 0xFF),
(int)((address >> 8) & 0xFF),
(int)(address & 0xFF) );
}
}else{
if( address == ANY_ADDRESS ){
std::sprintf( s, "<any>:%d", port );
}else{
std::sprintf( s, "%d.%d.%d.%d:%d",
(int)((address >> 24) & 0xFF),
(int)((address >> 16) & 0xFF),
(int)((address >> 8) & 0xFF),
(int)(address & 0xFF),
(int)port );
}
}
}

View File

@@ -0,0 +1,83 @@
/*
oscpack -- Open Sound Control (OSC) packet manipulation library
http://www.rossbencina.com/code/oscpack
Copyright (c) 2004-2013 Ross Bencina <rossb@audiomulch.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
The text above constitutes the entire oscpack license; however,
the oscpack developer(s) also make the following non-binding requests:
Any person wishing to distribute modifications to the Software is
requested to send the modifications to the original developer so that
they can be incorporated into the canonical version. It is also
requested that these non-binding requests be included whenever the
above license is reproduced.
*/
#ifndef INCLUDED_OSCPACK_IPENDPOINTNAME_H
#define INCLUDED_OSCPACK_IPENDPOINTNAME_H
class IpEndpointName{
static unsigned long GetHostByName( const char *s );
public:
static const unsigned long ANY_ADDRESS = 0xFFFFFFFF;
static const int ANY_PORT = -1;
IpEndpointName()
: address( ANY_ADDRESS ), port( ANY_PORT ) {}
IpEndpointName( int port_ )
: address( ANY_ADDRESS ), port( port_ ) {}
IpEndpointName( unsigned long ipAddress_, int port_ )
: address( ipAddress_ ), port( port_ ) {}
IpEndpointName( const char *addressName, int port_=ANY_PORT )
: address( GetHostByName( addressName ) )
, port( port_ ) {}
IpEndpointName( int addressA, int addressB, int addressC, int addressD, int port_=ANY_PORT )
: address( ( (addressA << 24) | (addressB << 16) | (addressC << 8) | addressD ) )
, port( port_ ) {}
// address and port are maintained in host byte order here
unsigned long address;
int port;
bool IsMulticastAddress() const { return ((address >> 24) & 0xFF) >= 224 && ((address >> 24) & 0xFF) <= 239; }
enum { ADDRESS_STRING_LENGTH=17 };
void AddressAsString( char *s ) const;
enum { ADDRESS_AND_PORT_STRING_LENGTH=23};
void AddressAndPortAsString( char *s ) const;
};
inline bool operator==( const IpEndpointName& lhs, const IpEndpointName& rhs )
{
return (lhs.address == rhs.address && lhs.port == rhs.port );
}
inline bool operator!=( const IpEndpointName& lhs, const IpEndpointName& rhs )
{
return !(lhs == rhs);
}
#endif /* INCLUDED_OSCPACK_IPENDPOINTNAME_H */

View File

@@ -0,0 +1,56 @@
/*
oscpack -- Open Sound Control (OSC) packet manipulation library
http://www.rossbencina.com/code/oscpack
Copyright (c) 2004-2013 Ross Bencina <rossb@audiomulch.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
The text above constitutes the entire oscpack license; however,
the oscpack developer(s) also make the following non-binding requests:
Any person wishing to distribute modifications to the Software is
requested to send the modifications to the original developer so that
they can be incorporated into the canonical version. It is also
requested that these non-binding requests be included whenever the
above license is reproduced.
*/
#ifndef INCLUDED_OSCPACK_NETWORKINGUTILS_H
#define INCLUDED_OSCPACK_NETWORKINGUTILS_H
// in general NetworkInitializer is only used internally, but if you're
// application creates multiple sockets from different threads at runtime you
// should instantiate one of these in main just to make sure the networking
// layer is initialized.
class NetworkInitializer{
public:
NetworkInitializer();
~NetworkInitializer();
};
// return ip address of host name in host byte order
unsigned long GetHostByName( const char *name );
#endif /* INCLUDED_OSCPACK_NETWORKINGUTILS_H */

View File

@@ -0,0 +1,50 @@
/*
oscpack -- Open Sound Control (OSC) packet manipulation library
http://www.rossbencina.com/code/oscpack
Copyright (c) 2004-2013 Ross Bencina <rossb@audiomulch.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
The text above constitutes the entire oscpack license; however,
the oscpack developer(s) also make the following non-binding requests:
Any person wishing to distribute modifications to the Software is
requested to send the modifications to the original developer so that
they can be incorporated into the canonical version. It is also
requested that these non-binding requests be included whenever the
above license is reproduced.
*/
#ifndef INCLUDED_OSCPACK_PACKETLISTENER_H
#define INCLUDED_OSCPACK_PACKETLISTENER_H
class IpEndpointName;
class PacketListener{
public:
virtual ~PacketListener() {}
virtual void ProcessPacket( const char *data, int size,
const IpEndpointName& remoteEndpoint ) = 0;
};
#endif /* INCLUDED_OSCPACK_PACKETLISTENER_H */

View File

@@ -0,0 +1,47 @@
/*
oscpack -- Open Sound Control (OSC) packet manipulation library
http://www.rossbencina.com/code/oscpack
Copyright (c) 2004-2013 Ross Bencina <rossb@audiomulch.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
The text above constitutes the entire oscpack license; however,
the oscpack developer(s) also make the following non-binding requests:
Any person wishing to distribute modifications to the Software is
requested to send the modifications to the original developer so that
they can be incorporated into the canonical version. It is also
requested that these non-binding requests be included whenever the
above license is reproduced.
*/
#ifndef INCLUDED_OSCPACK_TIMERLISTENER_H
#define INCLUDED_OSCPACK_TIMERLISTENER_H
class TimerListener{
public:
virtual ~TimerListener() {}
virtual void TimerExpired() = 0;
};
#endif /* INCLUDED_OSCPACK_TIMERLISTENER_H */

176
ext/OSCPack/ip/UdpSocket.h Normal file
View File

@@ -0,0 +1,176 @@
/*
oscpack -- Open Sound Control (OSC) packet manipulation library
http://www.rossbencina.com/code/oscpack
Copyright (c) 2004-2013 Ross Bencina <rossb@audiomulch.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
The text above constitutes the entire oscpack license; however,
the oscpack developer(s) also make the following non-binding requests:
Any person wishing to distribute modifications to the Software is
requested to send the modifications to the original developer so that
they can be incorporated into the canonical version. It is also
requested that these non-binding requests be included whenever the
above license is reproduced.
*/
#ifndef INCLUDED_OSCPACK_UDPSOCKET_H
#define INCLUDED_OSCPACK_UDPSOCKET_H
#include <cstring> // size_t
#include "NetworkingUtils.h"
#include "IpEndpointName.h"
class PacketListener;
class TimerListener;
class UdpSocket;
class SocketReceiveMultiplexer{
class Implementation;
Implementation *impl_;
friend class UdpSocket;
public:
SocketReceiveMultiplexer();
~SocketReceiveMultiplexer();
// only call the attach/detach methods _before_ calling Run
// only one listener per socket, each socket at most once
void AttachSocketListener( UdpSocket *socket, PacketListener *listener );
void DetachSocketListener( UdpSocket *socket, PacketListener *listener );
void AttachPeriodicTimerListener( int periodMilliseconds, TimerListener *listener );
void AttachPeriodicTimerListener(
int initialDelayMilliseconds, int periodMilliseconds, TimerListener *listener );
void DetachPeriodicTimerListener( TimerListener *listener );
void Run(); // loop and block processing messages indefinitely
void RunUntilSigInt();
void Break(); // call this from a listener to exit once the listener returns
void AsynchronousBreak(); // call this from another thread or signal handler to exit the Run() state
};
class UdpSocket{
class Implementation;
Implementation *impl_;
friend class SocketReceiveMultiplexer::Implementation;
public:
// Ctor throws std::runtime_error if there's a problem
// initializing the socket.
UdpSocket();
virtual ~UdpSocket();
// Enable broadcast addresses (e.g. x.x.x.255)
// Sets SO_BROADCAST socket option.
void SetEnableBroadcast( bool enableBroadcast );
// Enable multiple listeners for a single port on same
// network interface*
// Sets SO_REUSEADDR (also SO_REUSEPORT on OS X).
// [*] The exact behavior of SO_REUSEADDR and
// SO_REUSEPORT is undefined for some common cases
// and may have drastically different behavior on different
// operating systems.
void SetAllowReuse( bool allowReuse );
// The socket is created in an unbound, unconnected state
// such a socket can only be used to send to an arbitrary
// address using SendTo(). To use Send() you need to first
// connect to a remote endpoint using Connect(). To use
// ReceiveFrom you need to first bind to a local endpoint
// using Bind().
// Retrieve the local endpoint name when sending to 'to'
IpEndpointName LocalEndpointFor( const IpEndpointName& remoteEndpoint ) const;
// Connect to a remote endpoint which is used as the target
// for calls to Send()
void Connect( const IpEndpointName& remoteEndpoint );
void Send( const char *data, std::size_t size );
void SendTo( const IpEndpointName& remoteEndpoint, const char *data, std::size_t size );
// Bind a local endpoint to receive incoming data. Endpoint
// can be 'any' for the system to choose an endpoint
void Bind( const IpEndpointName& localEndpoint );
bool IsBound() const;
std::size_t ReceiveFrom( IpEndpointName& remoteEndpoint, char *data, std::size_t size );
};
// convenience classes for transmitting and receiving
// they just call Connect and/or Bind in the ctor.
// note that you can still use a receive socket
// for transmitting etc
class UdpTransmitSocket : public UdpSocket{
public:
UdpTransmitSocket( const IpEndpointName& remoteEndpoint )
{ Connect( remoteEndpoint ); }
};
class UdpReceiveSocket : public UdpSocket{
public:
UdpReceiveSocket( const IpEndpointName& localEndpoint )
{ Bind( localEndpoint ); }
};
// UdpListeningReceiveSocket provides a simple way to bind one listener
// to a single socket without having to manually set up a SocketReceiveMultiplexer
class UdpListeningReceiveSocket : public UdpSocket{
SocketReceiveMultiplexer mux_;
PacketListener *listener_;
public:
UdpListeningReceiveSocket( const IpEndpointName& localEndpoint, PacketListener *listener )
: listener_( listener )
{
Bind( localEndpoint );
mux_.AttachSocketListener( this, listener_ );
}
~UdpListeningReceiveSocket()
{ mux_.DetachSocketListener( this, listener_ ); }
// see SocketReceiveMultiplexer above for the behaviour of these methods...
void Run() { mux_.Run(); }
void RunUntilSigInt() { mux_.RunUntilSigInt(); }
void Break() { mux_.Break(); }
void AsynchronousBreak() { mux_.AsynchronousBreak(); }
};
#endif /* INCLUDED_OSCPACK_UDPSOCKET_H */

View File

@@ -0,0 +1,64 @@
/*
oscpack -- Open Sound Control (OSC) packet manipulation library
http://www.rossbencina.com/code/oscpack
Copyright (c) 2004-2013 Ross Bencina <rossb@audiomulch.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
The text above constitutes the entire oscpack license; however,
the oscpack developer(s) also make the following non-binding requests:
Any person wishing to distribute modifications to the Software is
requested to send the modifications to the original developer so that
they can be incorporated into the canonical version. It is also
requested that these non-binding requests be included whenever the
above license is reproduced.
*/
#include "ip/NetworkingUtils.h"
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <cstring>
NetworkInitializer::NetworkInitializer() {}
NetworkInitializer::~NetworkInitializer() {}
unsigned long GetHostByName( const char *name )
{
unsigned long result = 0;
struct hostent *h = gethostbyname( name );
if( h ){
struct in_addr a;
std::memcpy( &a, h->h_addr_list[0], h->h_length );
result = ntohl(a.s_addr);
}
return result;
}

View File

@@ -0,0 +1,602 @@
/*
oscpack -- Open Sound Control (OSC) packet manipulation library
http://www.rossbencina.com/code/oscpack
Copyright (c) 2004-2013 Ross Bencina <rossb@audiomulch.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
The text above constitutes the entire oscpack license; however,
the oscpack developer(s) also make the following non-binding requests:
Any person wishing to distribute modifications to the Software is
requested to send the modifications to the original developer so that
they can be incorporated into the canonical version. It is also
requested that these non-binding requests be included whenever the
above license is reproduced.
*/
#include "ip/UdpSocket.h"
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h> // for sockaddr_in
#include <signal.h>
#include <math.h>
#include <errno.h>
#include <string.h>
#include <algorithm>
#include <cassert>
#include <cstring> // for memset
#include <stdexcept>
#include <vector>
#include "ip/PacketListener.h"
#include "ip/TimerListener.h"
#if defined(__APPLE__) && !defined(_SOCKLEN_T)
// pre system 10.3 didn't have socklen_t
typedef ssize_t socklen_t;
#endif
static void SockaddrFromIpEndpointName( struct sockaddr_in& sockAddr, const IpEndpointName& endpoint )
{
std::memset( (char *)&sockAddr, 0, sizeof(sockAddr ) );
sockAddr.sin_family = AF_INET;
sockAddr.sin_addr.s_addr =
(endpoint.address == IpEndpointName::ANY_ADDRESS)
? INADDR_ANY
: htonl( endpoint.address );
sockAddr.sin_port =
(endpoint.port == IpEndpointName::ANY_PORT)
? 0
: htons( endpoint.port );
}
static IpEndpointName IpEndpointNameFromSockaddr( const struct sockaddr_in& sockAddr )
{
return IpEndpointName(
(sockAddr.sin_addr.s_addr == INADDR_ANY)
? IpEndpointName::ANY_ADDRESS
: ntohl( sockAddr.sin_addr.s_addr ),
(sockAddr.sin_port == 0)
? IpEndpointName::ANY_PORT
: ntohs( sockAddr.sin_port )
);
}
class UdpSocket::Implementation{
bool isBound_;
bool isConnected_;
int socket_;
struct sockaddr_in connectedAddr_;
struct sockaddr_in sendToAddr_;
public:
Implementation()
: isBound_( false )
, isConnected_( false )
, socket_( -1 )
{
if( (socket_ = socket( AF_INET, SOCK_DGRAM, 0 )) == -1 ){
throw std::runtime_error("unable to create udp socket\n");
}
std::memset( &sendToAddr_, 0, sizeof(sendToAddr_) );
sendToAddr_.sin_family = AF_INET;
}
~Implementation()
{
if (socket_ != -1) close(socket_);
}
void SetEnableBroadcast( bool enableBroadcast )
{
int broadcast = (enableBroadcast) ? 1 : 0; // int on posix
setsockopt(socket_, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast));
}
void SetAllowReuse( bool allowReuse )
{
int reuseAddr = (allowReuse) ? 1 : 0; // int on posix
setsockopt(socket_, SOL_SOCKET, SO_REUSEADDR, &reuseAddr, sizeof(reuseAddr));
#ifdef __APPLE__
// needed also for OS X - enable multiple listeners for a single port on same network interface
int reusePort = (allowReuse) ? 1 : 0; // int on posix
setsockopt(socket_, SOL_SOCKET, SO_REUSEPORT, &reusePort, sizeof(reusePort));
#endif
}
IpEndpointName LocalEndpointFor( const IpEndpointName& remoteEndpoint ) const
{
assert( isBound_ );
// first connect the socket to the remote server
struct sockaddr_in connectSockAddr;
SockaddrFromIpEndpointName( connectSockAddr, remoteEndpoint );
if (connect(socket_, (struct sockaddr *)&connectSockAddr, sizeof(connectSockAddr)) < 0) {
throw std::runtime_error("unable to connect udp socket\n");
}
// get the address
struct sockaddr_in sockAddr;
std::memset( (char *)&sockAddr, 0, sizeof(sockAddr ) );
socklen_t length = sizeof(sockAddr);
if (getsockname(socket_, (struct sockaddr *)&sockAddr, &length) < 0) {
throw std::runtime_error("unable to getsockname\n");
}
if( isConnected_ ){
// reconnect to the connected address
if (connect(socket_, (struct sockaddr *)&connectedAddr_, sizeof(connectedAddr_)) < 0) {
throw std::runtime_error("unable to connect udp socket\n");
}
}else{
// unconnect from the remote address
struct sockaddr_in unconnectSockAddr;
std::memset( (char *)&unconnectSockAddr, 0, sizeof(unconnectSockAddr ) );
unconnectSockAddr.sin_family = AF_UNSPEC;
// address fields are zero
int connectResult = connect(socket_, (struct sockaddr *)&unconnectSockAddr, sizeof(unconnectSockAddr));
if ( connectResult < 0 && errno != EAFNOSUPPORT ) {
throw std::runtime_error("unable to un-connect udp socket\n");
}
}
return IpEndpointNameFromSockaddr( sockAddr );
}
void Connect( const IpEndpointName& remoteEndpoint )
{
SockaddrFromIpEndpointName( connectedAddr_, remoteEndpoint );
if (connect(socket_, (struct sockaddr *)&connectedAddr_, sizeof(connectedAddr_)) < 0) {
throw std::runtime_error("unable to connect udp socket\n");
}
isConnected_ = true;
}
void Send( const char *data, std::size_t size )
{
assert( isConnected_ );
send( socket_, data, size, 0 );
}
void SendTo( const IpEndpointName& remoteEndpoint, const char *data, std::size_t size )
{
sendToAddr_.sin_addr.s_addr = htonl( remoteEndpoint.address );
sendToAddr_.sin_port = htons( remoteEndpoint.port );
sendto( socket_, data, size, 0, (sockaddr*)&sendToAddr_, sizeof(sendToAddr_) );
}
void Bind( const IpEndpointName& localEndpoint )
{
struct sockaddr_in bindSockAddr;
SockaddrFromIpEndpointName( bindSockAddr, localEndpoint );
if (bind(socket_, (struct sockaddr *)&bindSockAddr, sizeof(bindSockAddr)) < 0) {
throw std::runtime_error("unable to bind udp socket\n");
}
isBound_ = true;
}
bool IsBound() const { return isBound_; }
std::size_t ReceiveFrom( IpEndpointName& remoteEndpoint, char *data, std::size_t size )
{
assert( isBound_ );
struct sockaddr_in fromAddr;
socklen_t fromAddrLen = sizeof(fromAddr);
ssize_t result = recvfrom(socket_, data, size, 0,
(struct sockaddr *) &fromAddr, (socklen_t*)&fromAddrLen);
if( result < 0 )
return 0;
remoteEndpoint.address = ntohl(fromAddr.sin_addr.s_addr);
remoteEndpoint.port = ntohs(fromAddr.sin_port);
return (std::size_t)result;
}
int Socket() { return socket_; }
};
UdpSocket::UdpSocket()
{
impl_ = new Implementation();
}
UdpSocket::~UdpSocket()
{
delete impl_;
}
void UdpSocket::SetEnableBroadcast( bool enableBroadcast )
{
impl_->SetEnableBroadcast( enableBroadcast );
}
void UdpSocket::SetAllowReuse( bool allowReuse )
{
impl_->SetAllowReuse( allowReuse );
}
IpEndpointName UdpSocket::LocalEndpointFor( const IpEndpointName& remoteEndpoint ) const
{
return impl_->LocalEndpointFor( remoteEndpoint );
}
void UdpSocket::Connect( const IpEndpointName& remoteEndpoint )
{
impl_->Connect( remoteEndpoint );
}
void UdpSocket::Send( const char *data, std::size_t size )
{
impl_->Send( data, size );
}
void UdpSocket::SendTo( const IpEndpointName& remoteEndpoint, const char *data, std::size_t size )
{
impl_->SendTo( remoteEndpoint, data, size );
}
void UdpSocket::Bind( const IpEndpointName& localEndpoint )
{
impl_->Bind( localEndpoint );
}
bool UdpSocket::IsBound() const
{
return impl_->IsBound();
}
std::size_t UdpSocket::ReceiveFrom( IpEndpointName& remoteEndpoint, char *data, std::size_t size )
{
return impl_->ReceiveFrom( remoteEndpoint, data, size );
}
struct AttachedTimerListener{
AttachedTimerListener( int id, int p, TimerListener *tl )
: initialDelayMs( id )
, periodMs( p )
, listener( tl ) {}
int initialDelayMs;
int periodMs;
TimerListener *listener;
};
static bool CompareScheduledTimerCalls(
const std::pair< double, AttachedTimerListener > & lhs, const std::pair< double, AttachedTimerListener > & rhs )
{
return lhs.first < rhs.first;
}
SocketReceiveMultiplexer *multiplexerInstanceToAbortWithSigInt_ = 0;
extern "C" /*static*/ void InterruptSignalHandler( int );
/*static*/ void InterruptSignalHandler( int )
{
multiplexerInstanceToAbortWithSigInt_->AsynchronousBreak();
signal( SIGINT, SIG_DFL );
}
class SocketReceiveMultiplexer::Implementation{
std::vector< std::pair< PacketListener*, UdpSocket* > > socketListeners_;
std::vector< AttachedTimerListener > timerListeners_;
volatile bool break_;
int breakPipe_[2]; // [0] is the reader descriptor and [1] the writer
double GetCurrentTimeMs() const
{
struct timeval t;
gettimeofday( &t, 0 );
return ((double)t.tv_sec*1000.) + ((double)t.tv_usec / 1000.);
}
public:
Implementation()
{
if( pipe(breakPipe_) != 0 )
throw std::runtime_error( "creation of asynchronous break pipes failed\n" );
}
~Implementation()
{
close( breakPipe_[0] );
close( breakPipe_[1] );
}
void AttachSocketListener( UdpSocket *socket, PacketListener *listener )
{
assert( std::find( socketListeners_.begin(), socketListeners_.end(), std::make_pair(listener, socket) ) == socketListeners_.end() );
// we don't check that the same socket has been added multiple times, even though this is an error
socketListeners_.push_back( std::make_pair( listener, socket ) );
}
void DetachSocketListener( UdpSocket *socket, PacketListener *listener )
{
std::vector< std::pair< PacketListener*, UdpSocket* > >::iterator i =
std::find( socketListeners_.begin(), socketListeners_.end(), std::make_pair(listener, socket) );
assert( i != socketListeners_.end() );
socketListeners_.erase( i );
}
void AttachPeriodicTimerListener( int periodMilliseconds, TimerListener *listener )
{
timerListeners_.push_back( AttachedTimerListener( periodMilliseconds, periodMilliseconds, listener ) );
}
void AttachPeriodicTimerListener( int initialDelayMilliseconds, int periodMilliseconds, TimerListener *listener )
{
timerListeners_.push_back( AttachedTimerListener( initialDelayMilliseconds, periodMilliseconds, listener ) );
}
void DetachPeriodicTimerListener( TimerListener *listener )
{
std::vector< AttachedTimerListener >::iterator i = timerListeners_.begin();
while( i != timerListeners_.end() ){
if( i->listener == listener )
break;
++i;
}
assert( i != timerListeners_.end() );
timerListeners_.erase( i );
}
void Run()
{
break_ = false;
char *data = 0;
try{
// configure the master fd_set for select()
fd_set masterfds, tempfds;
FD_ZERO( &masterfds );
FD_ZERO( &tempfds );
// in addition to listening to the inbound sockets we
// also listen to the asynchronous break pipe, so that AsynchronousBreak()
// can break us out of select() from another thread.
FD_SET( breakPipe_[0], &masterfds );
int fdmax = breakPipe_[0];
for( std::vector< std::pair< PacketListener*, UdpSocket* > >::iterator i = socketListeners_.begin();
i != socketListeners_.end(); ++i ){
if( fdmax < i->second->impl_->Socket() )
fdmax = i->second->impl_->Socket();
FD_SET( i->second->impl_->Socket(), &masterfds );
}
// configure the timer queue
double currentTimeMs = GetCurrentTimeMs();
// expiry time ms, listener
std::vector< std::pair< double, AttachedTimerListener > > timerQueue_;
for( std::vector< AttachedTimerListener >::iterator i = timerListeners_.begin();
i != timerListeners_.end(); ++i )
timerQueue_.push_back( std::make_pair( currentTimeMs + i->initialDelayMs, *i ) );
std::sort( timerQueue_.begin(), timerQueue_.end(), CompareScheduledTimerCalls );
const int MAX_BUFFER_SIZE = 4098;
data = new char[ MAX_BUFFER_SIZE ];
IpEndpointName remoteEndpoint;
struct timeval timeout;
while( !break_ ){
tempfds = masterfds;
struct timeval *timeoutPtr = 0;
if( !timerQueue_.empty() ){
double timeoutMs = timerQueue_.front().first - GetCurrentTimeMs();
if( timeoutMs < 0 )
timeoutMs = 0;
long timoutSecondsPart = (long)(timeoutMs * .001);
timeout.tv_sec = (time_t)timoutSecondsPart;
// 1000000 microseconds in a second
timeout.tv_usec = (suseconds_t)((timeoutMs - (timoutSecondsPart * 1000)) * 1000);
timeoutPtr = &timeout;
}
if( select( fdmax + 1, &tempfds, 0, 0, timeoutPtr ) < 0 ){
if( break_ ){
break;
}else if( errno == EINTR ){
// on returning an error, select() doesn't clear tempfds.
// so tempfds would remain all set, which would cause read( breakPipe_[0]...
// below to block indefinitely. therefore if select returns EINTR we restart
// the while() loop instead of continuing on to below.
continue;
}else{
throw std::runtime_error("select failed\n");
}
}
if( FD_ISSET( breakPipe_[0], &tempfds ) ){
// clear pending data from the asynchronous break pipe
char c;
read( breakPipe_[0], &c, 1 );
}
if( break_ )
break;
for( std::vector< std::pair< PacketListener*, UdpSocket* > >::iterator i = socketListeners_.begin();
i != socketListeners_.end(); ++i ){
if( FD_ISSET( i->second->impl_->Socket(), &tempfds ) ){
std::size_t size = i->second->ReceiveFrom( remoteEndpoint, data, MAX_BUFFER_SIZE );
if( size > 0 ){
i->first->ProcessPacket( data, (int)size, remoteEndpoint );
if( break_ )
break;
}
}
}
// execute any expired timers
currentTimeMs = GetCurrentTimeMs();
bool resort = false;
for( std::vector< std::pair< double, AttachedTimerListener > >::iterator i = timerQueue_.begin();
i != timerQueue_.end() && i->first <= currentTimeMs; ++i ){
i->second.listener->TimerExpired();
if( break_ )
break;
i->first += i->second.periodMs;
resort = true;
}
if( resort )
std::sort( timerQueue_.begin(), timerQueue_.end(), CompareScheduledTimerCalls );
}
delete [] data;
}catch(...){
if( data )
delete [] data;
throw;
}
}
void Break()
{
break_ = true;
}
void AsynchronousBreak()
{
break_ = true;
// Send a termination message to the asynchronous break pipe, so select() will return
write( breakPipe_[1], "!", 1 );
}
};
SocketReceiveMultiplexer::SocketReceiveMultiplexer()
{
impl_ = new Implementation();
}
SocketReceiveMultiplexer::~SocketReceiveMultiplexer()
{
delete impl_;
}
void SocketReceiveMultiplexer::AttachSocketListener( UdpSocket *socket, PacketListener *listener )
{
impl_->AttachSocketListener( socket, listener );
}
void SocketReceiveMultiplexer::DetachSocketListener( UdpSocket *socket, PacketListener *listener )
{
impl_->DetachSocketListener( socket, listener );
}
void SocketReceiveMultiplexer::AttachPeriodicTimerListener( int periodMilliseconds, TimerListener *listener )
{
impl_->AttachPeriodicTimerListener( periodMilliseconds, listener );
}
void SocketReceiveMultiplexer::AttachPeriodicTimerListener( int initialDelayMilliseconds, int periodMilliseconds, TimerListener *listener )
{
impl_->AttachPeriodicTimerListener( initialDelayMilliseconds, periodMilliseconds, listener );
}
void SocketReceiveMultiplexer::DetachPeriodicTimerListener( TimerListener *listener )
{
impl_->DetachPeriodicTimerListener( listener );
}
void SocketReceiveMultiplexer::Run()
{
impl_->Run();
}
void SocketReceiveMultiplexer::RunUntilSigInt()
{
assert( multiplexerInstanceToAbortWithSigInt_ == 0 ); /* at present we support only one multiplexer instance running until sig int */
multiplexerInstanceToAbortWithSigInt_ = this;
signal( SIGINT, InterruptSignalHandler );
impl_->Run();
signal( SIGINT, SIG_DFL );
multiplexerInstanceToAbortWithSigInt_ = 0;
}
void SocketReceiveMultiplexer::Break()
{
impl_->Break();
}
void SocketReceiveMultiplexer::AsynchronousBreak()
{
impl_->AsynchronousBreak();
}

View File

@@ -0,0 +1,95 @@
/*
oscpack -- Open Sound Control (OSC) packet manipulation library
http://www.rossbencina.com/code/oscpack
Copyright (c) 2004-2013 Ross Bencina <rossb@audiomulch.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
The text above constitutes the entire oscpack license; however,
the oscpack developer(s) also make the following non-binding requests:
Any person wishing to distribute modifications to the Software is
requested to send the modifications to the original developer so that
they can be incorporated into the canonical version. It is also
requested that these non-binding requests be included whenever the
above license is reproduced.
*/
#include "ip/NetworkingUtils.h"
#include <winsock2.h> // this must come first to prevent errors with MSVC7
#include <windows.h>
#include <cstring>
static LONG initCount_ = 0;
static bool winsockInitialized_ = false;
NetworkInitializer::NetworkInitializer()
{
if( InterlockedIncrement( &initCount_ ) == 1 ){
// there is a race condition here if one thread tries to access
// the library while another is still initializing it.
// i can't think of an easy way to fix it so i'm telling you here
// incase you need to init the library from two threads at once.
// this is why the header file advises to instantiate one of these
// in main() so that the initialization happens globally
// initialize winsock
WSAData wsaData;
int nCode = WSAStartup(MAKEWORD(1, 1), &wsaData);
if( nCode != 0 ){
//std::cout << "WSAStartup() failed with error code " << nCode << "\n";
}else{
winsockInitialized_ = true;
}
}
}
NetworkInitializer::~NetworkInitializer()
{
if( InterlockedDecrement( &initCount_ ) == 0 ){
if( winsockInitialized_ ){
WSACleanup();
winsockInitialized_ = false;
}
}
}
unsigned long GetHostByName( const char *name )
{
NetworkInitializer networkInitializer;
unsigned long result = 0;
struct hostent *h = gethostbyname( name );
if( h ){
struct in_addr a;
std::memcpy( &a, h->h_addr_list[0], h->h_length );
result = ntohl(a.s_addr);
}
return result;
}

View File

@@ -0,0 +1,571 @@
/*
oscpack -- Open Sound Control (OSC) packet manipulation library
http://www.rossbencina.com/code/oscpack
Copyright (c) 2004-2013 Ross Bencina <rossb@audiomulch.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
The text above constitutes the entire oscpack license; however,
the oscpack developer(s) also make the following non-binding requests:
Any person wishing to distribute modifications to the Software is
requested to send the modifications to the original developer so that
they can be incorporated into the canonical version. It is also
requested that these non-binding requests be included whenever the
above license is reproduced.
*/
#include <winsock2.h> // this must come first to prevent errors with MSVC7
#include <windows.h>
#include <mmsystem.h> // for timeGetTime()
#ifndef WINCE
#include <signal.h>
#endif
#include <algorithm>
#include <cassert>
#include <cstring> // for memset
#include <stdexcept>
#include <vector>
#include "ip/UdpSocket.h" // usually I'd include the module header first
// but this is causing conflicts with BCB4 due to
// std::size_t usage.
#include "ip/NetworkingUtils.h"
#include "ip/PacketListener.h"
#include "ip/TimerListener.h"
typedef int socklen_t;
static void SockaddrFromIpEndpointName( struct sockaddr_in& sockAddr, const IpEndpointName& endpoint )
{
std::memset( (char *)&sockAddr, 0, sizeof(sockAddr ) );
sockAddr.sin_family = AF_INET;
sockAddr.sin_addr.s_addr =
(endpoint.address == IpEndpointName::ANY_ADDRESS)
? INADDR_ANY
: htonl( endpoint.address );
sockAddr.sin_port =
(endpoint.port == IpEndpointName::ANY_PORT)
? (short)0
: htons( (short)endpoint.port );
}
static IpEndpointName IpEndpointNameFromSockaddr( const struct sockaddr_in& sockAddr )
{
return IpEndpointName(
(sockAddr.sin_addr.s_addr == INADDR_ANY)
? IpEndpointName::ANY_ADDRESS
: ntohl( sockAddr.sin_addr.s_addr ),
(sockAddr.sin_port == 0)
? IpEndpointName::ANY_PORT
: ntohs( sockAddr.sin_port )
);
}
class UdpSocket::Implementation{
NetworkInitializer networkInitializer_;
bool isBound_;
bool isConnected_;
SOCKET socket_;
struct sockaddr_in connectedAddr_;
struct sockaddr_in sendToAddr_;
public:
Implementation()
: isBound_( false )
, isConnected_( false )
, socket_( INVALID_SOCKET )
{
if( (socket_ = socket( AF_INET, SOCK_DGRAM, 0 )) == INVALID_SOCKET ){
throw std::runtime_error("unable to create udp socket\n");
}
std::memset( &sendToAddr_, 0, sizeof(sendToAddr_) );
sendToAddr_.sin_family = AF_INET;
}
~Implementation()
{
if (socket_ != INVALID_SOCKET) closesocket(socket_);
}
void SetEnableBroadcast( bool enableBroadcast )
{
char broadcast = (char)((enableBroadcast) ? 1 : 0); // char on win32
setsockopt(socket_, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast));
}
void SetAllowReuse( bool allowReuse )
{
// Note: SO_REUSEADDR is non-deterministic for listening sockets on Win32. See MSDN article:
// "Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE"
// http://msdn.microsoft.com/en-us/library/ms740621%28VS.85%29.aspx
char reuseAddr = (char)((allowReuse) ? 1 : 0); // char on win32
setsockopt(socket_, SOL_SOCKET, SO_REUSEADDR, &reuseAddr, sizeof(reuseAddr));
}
IpEndpointName LocalEndpointFor( const IpEndpointName& remoteEndpoint ) const
{
assert( isBound_ );
// first connect the socket to the remote server
struct sockaddr_in connectSockAddr;
SockaddrFromIpEndpointName( connectSockAddr, remoteEndpoint );
if (connect(socket_, (struct sockaddr *)&connectSockAddr, sizeof(connectSockAddr)) < 0) {
throw std::runtime_error("unable to connect udp socket\n");
}
// get the address
struct sockaddr_in sockAddr;
std::memset( (char *)&sockAddr, 0, sizeof(sockAddr ) );
socklen_t length = sizeof(sockAddr);
if (getsockname(socket_, (struct sockaddr *)&sockAddr, &length) < 0) {
throw std::runtime_error("unable to getsockname\n");
}
if( isConnected_ ){
// reconnect to the connected address
if (connect(socket_, (struct sockaddr *)&connectedAddr_, sizeof(connectedAddr_)) < 0) {
throw std::runtime_error("unable to connect udp socket\n");
}
}else{
// unconnect from the remote address
struct sockaddr_in unconnectSockAddr;
SockaddrFromIpEndpointName( unconnectSockAddr, IpEndpointName() );
if( connect(socket_, (struct sockaddr *)&unconnectSockAddr, sizeof(unconnectSockAddr)) < 0
&& WSAGetLastError() != WSAEADDRNOTAVAIL ){
throw std::runtime_error("unable to un-connect udp socket\n");
}
}
return IpEndpointNameFromSockaddr( sockAddr );
}
void Connect( const IpEndpointName& remoteEndpoint )
{
SockaddrFromIpEndpointName( connectedAddr_, remoteEndpoint );
if (connect(socket_, (struct sockaddr *)&connectedAddr_, sizeof(connectedAddr_)) < 0) {
throw std::runtime_error("unable to connect udp socket\n");
}
isConnected_ = true;
}
void Send( const char *data, std::size_t size )
{
assert( isConnected_ );
send( socket_, data, (int)size, 0 );
}
void SendTo( const IpEndpointName& remoteEndpoint, const char *data, std::size_t size )
{
sendToAddr_.sin_addr.s_addr = htonl( remoteEndpoint.address );
sendToAddr_.sin_port = htons( (short)remoteEndpoint.port );
sendto( socket_, data, (int)size, 0, (sockaddr*)&sendToAddr_, sizeof(sendToAddr_) );
}
void Bind( const IpEndpointName& localEndpoint )
{
struct sockaddr_in bindSockAddr;
SockaddrFromIpEndpointName( bindSockAddr, localEndpoint );
if (bind(socket_, (struct sockaddr *)&bindSockAddr, sizeof(bindSockAddr)) < 0) {
throw std::runtime_error("unable to bind udp socket\n");
}
isBound_ = true;
}
bool IsBound() const { return isBound_; }
std::size_t ReceiveFrom( IpEndpointName& remoteEndpoint, char *data, std::size_t size )
{
assert( isBound_ );
struct sockaddr_in fromAddr;
socklen_t fromAddrLen = sizeof(fromAddr);
int result = recvfrom(socket_, data, (int)size, 0,
(struct sockaddr *) &fromAddr, (socklen_t*)&fromAddrLen);
if( result < 0 )
return 0;
remoteEndpoint.address = ntohl(fromAddr.sin_addr.s_addr);
remoteEndpoint.port = ntohs(fromAddr.sin_port);
return result;
}
SOCKET& Socket() { return socket_; }
};
UdpSocket::UdpSocket()
{
impl_ = new Implementation();
}
UdpSocket::~UdpSocket()
{
delete impl_;
}
void UdpSocket::SetEnableBroadcast( bool enableBroadcast )
{
impl_->SetEnableBroadcast( enableBroadcast );
}
void UdpSocket::SetAllowReuse( bool allowReuse )
{
impl_->SetAllowReuse( allowReuse );
}
IpEndpointName UdpSocket::LocalEndpointFor( const IpEndpointName& remoteEndpoint ) const
{
return impl_->LocalEndpointFor( remoteEndpoint );
}
void UdpSocket::Connect( const IpEndpointName& remoteEndpoint )
{
impl_->Connect( remoteEndpoint );
}
void UdpSocket::Send( const char *data, std::size_t size )
{
impl_->Send( data, size );
}
void UdpSocket::SendTo( const IpEndpointName& remoteEndpoint, const char *data, std::size_t size )
{
impl_->SendTo( remoteEndpoint, data, size );
}
void UdpSocket::Bind( const IpEndpointName& localEndpoint )
{
impl_->Bind( localEndpoint );
}
bool UdpSocket::IsBound() const
{
return impl_->IsBound();
}
std::size_t UdpSocket::ReceiveFrom( IpEndpointName& remoteEndpoint, char *data, std::size_t size )
{
return impl_->ReceiveFrom( remoteEndpoint, data, size );
}
struct AttachedTimerListener{
AttachedTimerListener( int id, int p, TimerListener *tl )
: initialDelayMs( id )
, periodMs( p )
, listener( tl ) {}
int initialDelayMs;
int periodMs;
TimerListener *listener;
};
static bool CompareScheduledTimerCalls(
const std::pair< double, AttachedTimerListener > & lhs, const std::pair< double, AttachedTimerListener > & rhs )
{
return lhs.first < rhs.first;
}
SocketReceiveMultiplexer *multiplexerInstanceToAbortWithSigInt_ = 0;
extern "C" /*static*/ void InterruptSignalHandler( int );
/*static*/ void InterruptSignalHandler( int )
{
multiplexerInstanceToAbortWithSigInt_->AsynchronousBreak();
#ifndef WINCE
signal( SIGINT, SIG_DFL );
#endif
}
class SocketReceiveMultiplexer::Implementation{
NetworkInitializer networkInitializer_;
std::vector< std::pair< PacketListener*, UdpSocket* > > socketListeners_;
std::vector< AttachedTimerListener > timerListeners_;
volatile bool break_;
HANDLE breakEvent_;
double GetCurrentTimeMs() const
{
#ifndef WINCE
return timeGetTime(); // FIXME: bad choice if you want to run for more than 40 days
#else
return 0;
#endif
}
public:
Implementation()
{
breakEvent_ = CreateEvent( NULL, FALSE, FALSE, NULL );
}
~Implementation()
{
CloseHandle( breakEvent_ );
}
void AttachSocketListener( UdpSocket *socket, PacketListener *listener )
{
assert( std::find( socketListeners_.begin(), socketListeners_.end(), std::make_pair(listener, socket) ) == socketListeners_.end() );
// we don't check that the same socket has been added multiple times, even though this is an error
socketListeners_.push_back( std::make_pair( listener, socket ) );
}
void DetachSocketListener( UdpSocket *socket, PacketListener *listener )
{
std::vector< std::pair< PacketListener*, UdpSocket* > >::iterator i =
std::find( socketListeners_.begin(), socketListeners_.end(), std::make_pair(listener, socket) );
assert( i != socketListeners_.end() );
socketListeners_.erase( i );
}
void AttachPeriodicTimerListener( int periodMilliseconds, TimerListener *listener )
{
timerListeners_.push_back( AttachedTimerListener( periodMilliseconds, periodMilliseconds, listener ) );
}
void AttachPeriodicTimerListener( int initialDelayMilliseconds, int periodMilliseconds, TimerListener *listener )
{
timerListeners_.push_back( AttachedTimerListener( initialDelayMilliseconds, periodMilliseconds, listener ) );
}
void DetachPeriodicTimerListener( TimerListener *listener )
{
std::vector< AttachedTimerListener >::iterator i = timerListeners_.begin();
while( i != timerListeners_.end() ){
if( i->listener == listener )
break;
++i;
}
assert( i != timerListeners_.end() );
timerListeners_.erase( i );
}
void Run()
{
break_ = false;
// prepare the window events which we use to wake up on incoming data
// we use this instead of select() primarily to support the AsyncBreak()
// mechanism.
std::vector<HANDLE> events( socketListeners_.size() + 1, 0 );
int j=0;
for( std::vector< std::pair< PacketListener*, UdpSocket* > >::iterator i = socketListeners_.begin();
i != socketListeners_.end(); ++i, ++j ){
HANDLE event = CreateEvent( NULL, FALSE, FALSE, NULL );
WSAEventSelect( i->second->impl_->Socket(), event, FD_READ ); // note that this makes the socket non-blocking which is why we can safely call RecieveFrom() on all sockets below
events[j] = event;
}
events[ socketListeners_.size() ] = breakEvent_; // last event in the collection is the break event
// configure the timer queue
double currentTimeMs = GetCurrentTimeMs();
// expiry time ms, listener
std::vector< std::pair< double, AttachedTimerListener > > timerQueue_;
for( std::vector< AttachedTimerListener >::iterator i = timerListeners_.begin();
i != timerListeners_.end(); ++i )
timerQueue_.push_back( std::make_pair( currentTimeMs + i->initialDelayMs, *i ) );
std::sort( timerQueue_.begin(), timerQueue_.end(), CompareScheduledTimerCalls );
const int MAX_BUFFER_SIZE = 4098;
char *data = new char[ MAX_BUFFER_SIZE ];
IpEndpointName remoteEndpoint;
while( !break_ ){
double currentTimeMs = GetCurrentTimeMs();
DWORD waitTime = INFINITE;
if( !timerQueue_.empty() ){
waitTime = (DWORD)( timerQueue_.front().first >= currentTimeMs
? timerQueue_.front().first - currentTimeMs
: 0 );
}
DWORD waitResult = WaitForMultipleObjects( (DWORD)socketListeners_.size() + 1, &events[0], FALSE, waitTime );
if( break_ )
break;
if( waitResult != WAIT_TIMEOUT ){
for( int i = waitResult - WAIT_OBJECT_0; i < (int)socketListeners_.size(); ++i ){
std::size_t size = socketListeners_[i].second->ReceiveFrom( remoteEndpoint, data, MAX_BUFFER_SIZE );
if( size > 0 ){
socketListeners_[i].first->ProcessPacket( data, (int)size, remoteEndpoint );
if( break_ )
break;
}
}
}
// execute any expired timers
currentTimeMs = GetCurrentTimeMs();
bool resort = false;
for( std::vector< std::pair< double, AttachedTimerListener > >::iterator i = timerQueue_.begin();
i != timerQueue_.end() && i->first <= currentTimeMs; ++i ){
i->second.listener->TimerExpired();
if( break_ )
break;
i->first += i->second.periodMs;
resort = true;
}
if( resort )
std::sort( timerQueue_.begin(), timerQueue_.end(), CompareScheduledTimerCalls );
}
delete [] data;
// free events
j = 0;
for( std::vector< std::pair< PacketListener*, UdpSocket* > >::iterator i = socketListeners_.begin();
i != socketListeners_.end(); ++i, ++j ){
WSAEventSelect( i->second->impl_->Socket(), events[j], 0 ); // remove association between socket and event
CloseHandle( events[j] );
unsigned long enableNonblocking = 0;
ioctlsocket( i->second->impl_->Socket(), FIONBIO, &enableNonblocking ); // make the socket blocking again
}
}
void Break()
{
break_ = true;
}
void AsynchronousBreak()
{
break_ = true;
SetEvent( breakEvent_ );
}
};
SocketReceiveMultiplexer::SocketReceiveMultiplexer()
{
impl_ = new Implementation();
}
SocketReceiveMultiplexer::~SocketReceiveMultiplexer()
{
delete impl_;
}
void SocketReceiveMultiplexer::AttachSocketListener( UdpSocket *socket, PacketListener *listener )
{
impl_->AttachSocketListener( socket, listener );
}
void SocketReceiveMultiplexer::DetachSocketListener( UdpSocket *socket, PacketListener *listener )
{
impl_->DetachSocketListener( socket, listener );
}
void SocketReceiveMultiplexer::AttachPeriodicTimerListener( int periodMilliseconds, TimerListener *listener )
{
impl_->AttachPeriodicTimerListener( periodMilliseconds, listener );
}
void SocketReceiveMultiplexer::AttachPeriodicTimerListener( int initialDelayMilliseconds, int periodMilliseconds, TimerListener *listener )
{
impl_->AttachPeriodicTimerListener( initialDelayMilliseconds, periodMilliseconds, listener );
}
void SocketReceiveMultiplexer::DetachPeriodicTimerListener( TimerListener *listener )
{
impl_->DetachPeriodicTimerListener( listener );
}
void SocketReceiveMultiplexer::Run()
{
impl_->Run();
}
void SocketReceiveMultiplexer::RunUntilSigInt()
{
assert( multiplexerInstanceToAbortWithSigInt_ == 0 ); /* at present we support only one multiplexer instance running until sig int */
multiplexerInstanceToAbortWithSigInt_ = this;
#ifndef WINCE
signal( SIGINT, InterruptSignalHandler );
#endif
impl_->Run();
#ifndef WINCE
signal( SIGINT, SIG_DFL );
#endif
multiplexerInstanceToAbortWithSigInt_ = 0;
}
void SocketReceiveMultiplexer::Break()
{
impl_->Break();
}
void SocketReceiveMultiplexer::AsynchronousBreak()
{
impl_->AsynchronousBreak();
}

View File

@@ -0,0 +1,80 @@
/*
oscpack -- Open Sound Control (OSC) packet manipulation library
http://www.rossbencina.com/code/oscpack
Copyright (c) 2004-2013 Ross Bencina <rossb@audiomulch.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
The text above constitutes the entire oscpack license; however,
the oscpack developer(s) also make the following non-binding requests:
Any person wishing to distribute modifications to the Software is
requested to send the modifications to the original developer so that
they can be incorporated into the canonical version. It is also
requested that these non-binding requests be included whenever the
above license is reproduced.
*/
#ifndef INCLUDED_OSCPACK_MESSAGEMAPPINGOSCPACKETLISTENER_H
#define INCLUDED_OSCPACK_MESSAGEMAPPINGOSCPACKETLISTENER_H
#include <cstring>
#include <map>
#include "OscPacketListener.h"
namespace osc{
template< class T >
class MessageMappingOscPacketListener : public OscPacketListener{
public:
typedef void (T::*function_type)(const osc::ReceivedMessage&, const IpEndpointName&);
protected:
void RegisterMessageFunction( const char *addressPattern, function_type f )
{
functions_.insert( std::make_pair( addressPattern, f ) );
}
virtual void ProcessMessage( const osc::ReceivedMessage& m,
const IpEndpointName& remoteEndpoint )
{
typename function_map_type::iterator i = functions_.find( m.AddressPattern() );
if( i != functions_.end() )
(dynamic_cast<T*>(this)->*(i->second))( m, remoteEndpoint );
}
private:
struct cstr_compare{
bool operator()( const char *lhs, const char *rhs ) const
{ return std::strcmp( lhs, rhs ) < 0; }
};
typedef std::map<const char*, function_type, cstr_compare> function_map_type;
function_map_type functions_;
};
} // namespace osc
#endif /* INCLUDED_OSCPACK_MESSAGEMAPPINGOSCPACKETLISTENER_H */

View File

@@ -0,0 +1,62 @@
/*
oscpack -- Open Sound Control (OSC) packet manipulation library
http://www.rossbencina.com/code/oscpack
Copyright (c) 2004-2013 Ross Bencina <rossb@audiomulch.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
The text above constitutes the entire oscpack license; however,
the oscpack developer(s) also make the following non-binding requests:
Any person wishing to distribute modifications to the Software is
requested to send the modifications to the original developer so that
they can be incorporated into the canonical version. It is also
requested that these non-binding requests be included whenever the
above license is reproduced.
*/
#ifndef INCLUDED_OSCPACK_OSCEXCEPTION_H
#define INCLUDED_OSCPACK_OSCEXCEPTION_H
#include <exception>
namespace osc{
class Exception : public std::exception {
const char *what_;
public:
Exception() throw() {}
Exception( const Exception& src ) throw()
: std::exception( src )
, what_( src.what_ ) {}
Exception( const char *w ) throw()
: what_( w ) {}
Exception& operator=( const Exception& src ) throw()
{ what_ = src.what_; return *this; }
virtual ~Exception() throw() {}
virtual const char* what() const throw() { return what_; }
};
} // namespace osc
#endif /* INCLUDED_OSCPACK_OSCEXCEPTION_H */

View File

@@ -0,0 +1,63 @@
/*
oscpack -- Open Sound Control packet manipulation library
http://www.audiomulch.com/~rossb/oscpack
Copyright (c) 2004-2005 Ross Bencina <rossb@audiomulch.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
Any person wishing to distribute modifications to the Software is
requested to send the modifications to the original developer so that
they can be incorporated into the canonical version.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef OSC_HOSTENDIANNESS_H
#define OSC_HOSTENDIANNESS_H
/*
Make sure either OSC_HOST_LITTLE_ENDIAN or OSC_HOST_BIG_ENDIAN is defined
If you know a way to enhance the detection below for Linux and/or MacOSX
please let me know! I've tried a few things which don't work.
*/
// you can define one of the above symbols from the command line
// then you don't have to edit this file.
#if defined(__WIN32__) || defined(WIN32)
// assume that __WIN32__ is only defined on little endian systems
#define OSC_HOST_LITTLE_ENDIAN 1
#undef OSC_HOST_BIG_ENDIAN
#else
#if defined(__LITTLE_ENDIAN__) || defined(LITTLE_ENDIAN) || (_BYTE_ORDER == _LITTLE_ENDIAN)
#define OSC_HOST_LITTLE_ENDIAN 1
#undef OSC_HOST_BIG_ENDIAN
#else
#define OSC_HOST_BIG_ENDIAN 1
#undef OSC_HOST_LITTLE_ENDIAN
#endif
#endif
#endif // OSC_HOSTENDIANNESS_H

View File

@@ -0,0 +1,683 @@
/*
oscpack -- Open Sound Control (OSC) packet manipulation library
http://www.rossbencina.com/code/oscpack
Copyright (c) 2004-2013 Ross Bencina <rossb@audiomulch.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
The text above constitutes the entire oscpack license; however,
the oscpack developer(s) also make the following non-binding requests:
Any person wishing to distribute modifications to the Software is
requested to send the modifications to the original developer so that
they can be incorporated into the canonical version. It is also
requested that these non-binding requests be included whenever the
above license is reproduced.
*/
#include "OscOutboundPacketStream.h"
#if defined(__WIN32__) || defined(WIN32) || defined(_WIN32)
#include <malloc.h> // for alloca
#else
//#include <alloca.h> // alloca on Linux (also OSX)
#include <stdlib.h> // alloca on OSX and FreeBSD (and Linux?)
#endif
#include <cassert>
#include <cstring> // memcpy, memmove, strcpy, strlen
#include <cstddef> // ptrdiff_t
#include "OscHostEndianness.h"
#if defined(__BORLANDC__) // workaround for BCB4 release build intrinsics bug
namespace std {
using ::__strcpy__; // avoid error: E2316 '__strcpy__' is not a member of 'std'.
}
#endif
namespace osc{
static void FromInt32( char *p, int32 x )
{
#ifdef OSC_HOST_LITTLE_ENDIAN
union{
osc::int32 i;
char c[4];
} u;
u.i = x;
p[3] = u.c[0];
p[2] = u.c[1];
p[1] = u.c[2];
p[0] = u.c[3];
#else
*reinterpret_cast<int32*>(p) = x;
#endif
}
static void FromUInt32( char *p, uint32 x )
{
#ifdef OSC_HOST_LITTLE_ENDIAN
union{
osc::uint32 i;
char c[4];
} u;
u.i = x;
p[3] = u.c[0];
p[2] = u.c[1];
p[1] = u.c[2];
p[0] = u.c[3];
#else
*reinterpret_cast<uint32*>(p) = x;
#endif
}
static void FromInt64( char *p, int64 x )
{
#ifdef OSC_HOST_LITTLE_ENDIAN
union{
osc::int64 i;
char c[8];
} u;
u.i = x;
p[7] = u.c[0];
p[6] = u.c[1];
p[5] = u.c[2];
p[4] = u.c[3];
p[3] = u.c[4];
p[2] = u.c[5];
p[1] = u.c[6];
p[0] = u.c[7];
#else
*reinterpret_cast<int64*>(p) = x;
#endif
}
static void FromUInt64( char *p, uint64 x )
{
#ifdef OSC_HOST_LITTLE_ENDIAN
union{
osc::uint64 i;
char c[8];
} u;
u.i = x;
p[7] = u.c[0];
p[6] = u.c[1];
p[5] = u.c[2];
p[4] = u.c[3];
p[3] = u.c[4];
p[2] = u.c[5];
p[1] = u.c[6];
p[0] = u.c[7];
#else
*reinterpret_cast<uint64*>(p) = x;
#endif
}
// round up to the next highest multiple of 4. unless x is already a multiple of 4
static inline std::size_t RoundUp4( std::size_t x )
{
return (x + 3) & ~((std::size_t)0x03);
}
OutboundPacketStream::OutboundPacketStream( char *buffer, std::size_t capacity )
: data_( buffer )
, end_( data_ + capacity )
, typeTagsCurrent_( end_ )
, messageCursor_( data_ )
, argumentCurrent_( data_ )
, elementSizePtr_( 0 )
, messageIsInProgress_( false )
{
// sanity check integer types declared in OscTypes.h
// you'll need to fix OscTypes.h if any of these asserts fail
assert( sizeof(osc::int32) == 4 );
assert( sizeof(osc::uint32) == 4 );
assert( sizeof(osc::int64) == 8 );
assert( sizeof(osc::uint64) == 8 );
}
OutboundPacketStream::~OutboundPacketStream()
{
}
char *OutboundPacketStream::BeginElement( char *beginPtr )
{
if( elementSizePtr_ == 0 ){
elementSizePtr_ = reinterpret_cast<uint32*>(data_);
return beginPtr;
}else{
// store an offset to the old element size ptr in the element size slot
// we store an offset rather than the actual pointer to be 64 bit clean.
*reinterpret_cast<uint32*>(beginPtr) =
(uint32)(reinterpret_cast<char*>(elementSizePtr_) - data_);
elementSizePtr_ = reinterpret_cast<uint32*>(beginPtr);
return beginPtr + 4;
}
}
void OutboundPacketStream::EndElement( char *endPtr )
{
assert( elementSizePtr_ != 0 );
if( elementSizePtr_ == reinterpret_cast<uint32*>(data_) ){
elementSizePtr_ = 0;
}else{
// while building an element, an offset to the containing element's
// size slot is stored in the elements size slot (or a ptr to data_
// if there is no containing element). We retrieve that here
uint32 *previousElementSizePtr =
reinterpret_cast<uint32*>(data_ + *elementSizePtr_);
// then we store the element size in the slot. note that the element
// size does not include the size slot, hence the - 4 below.
std::ptrdiff_t d = endPtr - reinterpret_cast<char*>(elementSizePtr_);
// assert( d >= 4 && d <= 0x7FFFFFFF ); // assume packets smaller than 2Gb
uint32 elementSize = static_cast<uint32>(d - 4);
FromUInt32( reinterpret_cast<char*>(elementSizePtr_), elementSize );
// finally, we reset the element size ptr to the containing element
elementSizePtr_ = previousElementSizePtr;
}
}
bool OutboundPacketStream::ElementSizeSlotRequired() const
{
return (elementSizePtr_ != 0);
}
void OutboundPacketStream::CheckForAvailableBundleSpace()
{
std::size_t required = Size() + ((ElementSizeSlotRequired())?4:0) + 16;
if( required > Capacity() )
throw OutOfBufferMemoryException();
}
void OutboundPacketStream::CheckForAvailableMessageSpace( const char *addressPattern )
{
// plus 4 for at least four bytes of type tag
std::size_t required = Size() + ((ElementSizeSlotRequired())?4:0)
+ RoundUp4(std::strlen(addressPattern) + 1) + 4;
if( required > Capacity() )
throw OutOfBufferMemoryException();
}
void OutboundPacketStream::CheckForAvailableArgumentSpace( std::size_t argumentLength )
{
// plus three for extra type tag, comma and null terminator
std::size_t required = (argumentCurrent_ - data_) + argumentLength
+ RoundUp4( (end_ - typeTagsCurrent_) + 3 );
if( required > Capacity() )
throw OutOfBufferMemoryException();
}
void OutboundPacketStream::Clear()
{
typeTagsCurrent_ = end_;
messageCursor_ = data_;
argumentCurrent_ = data_;
elementSizePtr_ = 0;
messageIsInProgress_ = false;
}
std::size_t OutboundPacketStream::Capacity() const
{
return end_ - data_;
}
std::size_t OutboundPacketStream::Size() const
{
std::size_t result = argumentCurrent_ - data_;
if( IsMessageInProgress() ){
// account for the length of the type tag string. the total type tag
// includes an initial comma, plus at least one terminating \0
result += RoundUp4( (end_ - typeTagsCurrent_) + 2 );
}
return result;
}
const char *OutboundPacketStream::Data() const
{
return data_;
}
bool OutboundPacketStream::IsReady() const
{
return (!IsMessageInProgress() && !IsBundleInProgress());
}
bool OutboundPacketStream::IsMessageInProgress() const
{
return messageIsInProgress_;
}
bool OutboundPacketStream::IsBundleInProgress() const
{
return (elementSizePtr_ != 0);
}
OutboundPacketStream& OutboundPacketStream::operator<<( const BundleInitiator& rhs )
{
if( IsMessageInProgress() )
throw MessageInProgressException();
CheckForAvailableBundleSpace();
messageCursor_ = BeginElement( messageCursor_ );
std::memcpy( messageCursor_, "#bundle\0", 8 );
FromUInt64( messageCursor_ + 8, rhs.timeTag );
messageCursor_ += 16;
argumentCurrent_ = messageCursor_;
return *this;
}
OutboundPacketStream& OutboundPacketStream::operator<<( const BundleTerminator& rhs )
{
(void) rhs;
if( !IsBundleInProgress() )
throw BundleNotInProgressException();
if( IsMessageInProgress() )
throw MessageInProgressException();
EndElement( messageCursor_ );
return *this;
}
OutboundPacketStream& OutboundPacketStream::operator<<( const BeginMessage& rhs )
{
if( IsMessageInProgress() )
throw MessageInProgressException();
CheckForAvailableMessageSpace( rhs.addressPattern );
messageCursor_ = BeginElement( messageCursor_ );
std::strcpy( messageCursor_, rhs.addressPattern );
std::size_t rhsLength = std::strlen(rhs.addressPattern);
messageCursor_ += rhsLength + 1;
// zero pad to 4-byte boundary
std::size_t i = rhsLength + 1;
while( i & 0x3 ){
*messageCursor_++ = '\0';
++i;
}
argumentCurrent_ = messageCursor_;
typeTagsCurrent_ = end_;
messageIsInProgress_ = true;
return *this;
}
OutboundPacketStream& OutboundPacketStream::operator<<( const MessageTerminator& rhs )
{
(void) rhs;
if( !IsMessageInProgress() )
throw MessageNotInProgressException();
std::size_t typeTagsCount = end_ - typeTagsCurrent_;
if( typeTagsCount ){
char *tempTypeTags = (char*)alloca(typeTagsCount);
std::memcpy( tempTypeTags, typeTagsCurrent_, typeTagsCount );
// slot size includes comma and null terminator
std::size_t typeTagSlotSize = RoundUp4( typeTagsCount + 2 );
std::size_t argumentsSize = argumentCurrent_ - messageCursor_;
std::memmove( messageCursor_ + typeTagSlotSize, messageCursor_, argumentsSize );
messageCursor_[0] = ',';
// copy type tags in reverse (really forward) order
for( std::size_t i=0; i < typeTagsCount; ++i )
messageCursor_[i+1] = tempTypeTags[ (typeTagsCount-1) - i ];
char *p = messageCursor_ + 1 + typeTagsCount;
for( std::size_t i=0; i < (typeTagSlotSize - (typeTagsCount + 1)); ++i )
*p++ = '\0';
typeTagsCurrent_ = end_;
// advance messageCursor_ for next message
messageCursor_ += typeTagSlotSize + argumentsSize;
}else{
// send an empty type tags string
std::memcpy( messageCursor_, ",\0\0\0", 4 );
// advance messageCursor_ for next message
messageCursor_ += 4;
}
argumentCurrent_ = messageCursor_;
EndElement( messageCursor_ );
messageIsInProgress_ = false;
return *this;
}
OutboundPacketStream& OutboundPacketStream::operator<<( bool rhs )
{
CheckForAvailableArgumentSpace(0);
*(--typeTagsCurrent_) = (char)((rhs) ? TRUE_TYPE_TAG : FALSE_TYPE_TAG);
return *this;
}
OutboundPacketStream& OutboundPacketStream::operator<<( const NilType& rhs )
{
(void) rhs;
CheckForAvailableArgumentSpace(0);
*(--typeTagsCurrent_) = NIL_TYPE_TAG;
return *this;
}
OutboundPacketStream& OutboundPacketStream::operator<<( const InfinitumType& rhs )
{
(void) rhs;
CheckForAvailableArgumentSpace(0);
*(--typeTagsCurrent_) = INFINITUM_TYPE_TAG;
return *this;
}
OutboundPacketStream& OutboundPacketStream::operator<<( int32 rhs )
{
CheckForAvailableArgumentSpace(4);
*(--typeTagsCurrent_) = INT32_TYPE_TAG;
FromInt32( argumentCurrent_, rhs );
argumentCurrent_ += 4;
return *this;
}
OutboundPacketStream& OutboundPacketStream::operator<<( float rhs )
{
CheckForAvailableArgumentSpace(4);
*(--typeTagsCurrent_) = FLOAT_TYPE_TAG;
#ifdef OSC_HOST_LITTLE_ENDIAN
union{
float f;
char c[4];
} u;
u.f = rhs;
argumentCurrent_[3] = u.c[0];
argumentCurrent_[2] = u.c[1];
argumentCurrent_[1] = u.c[2];
argumentCurrent_[0] = u.c[3];
#else
*reinterpret_cast<float*>(argumentCurrent_) = rhs;
#endif
argumentCurrent_ += 4;
return *this;
}
OutboundPacketStream& OutboundPacketStream::operator<<( char rhs )
{
CheckForAvailableArgumentSpace(4);
*(--typeTagsCurrent_) = CHAR_TYPE_TAG;
FromInt32( argumentCurrent_, rhs );
argumentCurrent_ += 4;
return *this;
}
OutboundPacketStream& OutboundPacketStream::operator<<( const RgbaColor& rhs )
{
CheckForAvailableArgumentSpace(4);
*(--typeTagsCurrent_) = RGBA_COLOR_TYPE_TAG;
FromUInt32( argumentCurrent_, rhs );
argumentCurrent_ += 4;
return *this;
}
OutboundPacketStream& OutboundPacketStream::operator<<( const MidiMessage& rhs )
{
CheckForAvailableArgumentSpace(4);
*(--typeTagsCurrent_) = MIDI_MESSAGE_TYPE_TAG;
FromUInt32( argumentCurrent_, rhs );
argumentCurrent_ += 4;
return *this;
}
OutboundPacketStream& OutboundPacketStream::operator<<( int64 rhs )
{
CheckForAvailableArgumentSpace(8);
*(--typeTagsCurrent_) = INT64_TYPE_TAG;
FromInt64( argumentCurrent_, rhs );
argumentCurrent_ += 8;
return *this;
}
OutboundPacketStream& OutboundPacketStream::operator<<( const TimeTag& rhs )
{
CheckForAvailableArgumentSpace(8);
*(--typeTagsCurrent_) = TIME_TAG_TYPE_TAG;
FromUInt64( argumentCurrent_, rhs );
argumentCurrent_ += 8;
return *this;
}
OutboundPacketStream& OutboundPacketStream::operator<<( double rhs )
{
CheckForAvailableArgumentSpace(8);
*(--typeTagsCurrent_) = DOUBLE_TYPE_TAG;
#ifdef OSC_HOST_LITTLE_ENDIAN
union{
double f;
char c[8];
} u;
u.f = rhs;
argumentCurrent_[7] = u.c[0];
argumentCurrent_[6] = u.c[1];
argumentCurrent_[5] = u.c[2];
argumentCurrent_[4] = u.c[3];
argumentCurrent_[3] = u.c[4];
argumentCurrent_[2] = u.c[5];
argumentCurrent_[1] = u.c[6];
argumentCurrent_[0] = u.c[7];
#else
*reinterpret_cast<double*>(argumentCurrent_) = rhs;
#endif
argumentCurrent_ += 8;
return *this;
}
OutboundPacketStream& OutboundPacketStream::operator<<( const char *rhs )
{
CheckForAvailableArgumentSpace( RoundUp4(std::strlen(rhs) + 1) );
*(--typeTagsCurrent_) = STRING_TYPE_TAG;
std::strcpy( argumentCurrent_, rhs );
std::size_t rhsLength = std::strlen(rhs);
argumentCurrent_ += rhsLength + 1;
// zero pad to 4-byte boundary
std::size_t i = rhsLength + 1;
while( i & 0x3 ){
*argumentCurrent_++ = '\0';
++i;
}
return *this;
}
OutboundPacketStream& OutboundPacketStream::operator<<( const Symbol& rhs )
{
CheckForAvailableArgumentSpace( RoundUp4(std::strlen(rhs) + 1) );
*(--typeTagsCurrent_) = SYMBOL_TYPE_TAG;
std::strcpy( argumentCurrent_, rhs );
std::size_t rhsLength = std::strlen(rhs);
argumentCurrent_ += rhsLength + 1;
// zero pad to 4-byte boundary
std::size_t i = rhsLength + 1;
while( i & 0x3 ){
*argumentCurrent_++ = '\0';
++i;
}
return *this;
}
OutboundPacketStream& OutboundPacketStream::operator<<( const Blob& rhs )
{
CheckForAvailableArgumentSpace( 4 + RoundUp4(rhs.size) );
*(--typeTagsCurrent_) = BLOB_TYPE_TAG;
FromUInt32( argumentCurrent_, rhs.size );
argumentCurrent_ += 4;
std::memcpy( argumentCurrent_, rhs.data, rhs.size );
argumentCurrent_ += rhs.size;
// zero pad to 4-byte boundary
unsigned long i = rhs.size;
while( i & 0x3 ){
*argumentCurrent_++ = '\0';
++i;
}
return *this;
}
OutboundPacketStream& OutboundPacketStream::operator<<( const ArrayInitiator& rhs )
{
(void) rhs;
CheckForAvailableArgumentSpace(0);
*(--typeTagsCurrent_) = ARRAY_BEGIN_TYPE_TAG;
return *this;
}
OutboundPacketStream& OutboundPacketStream::operator<<( const ArrayTerminator& rhs )
{
(void) rhs;
CheckForAvailableArgumentSpace(0);
*(--typeTagsCurrent_) = ARRAY_END_TYPE_TAG;
return *this;
}
} // namespace osc

View File

@@ -0,0 +1,154 @@
/*
oscpack -- Open Sound Control (OSC) packet manipulation library
http://www.rossbencina.com/code/oscpack
Copyright (c) 2004-2013 Ross Bencina <rossb@audiomulch.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
The text above constitutes the entire oscpack license; however,
the oscpack developer(s) also make the following non-binding requests:
Any person wishing to distribute modifications to the Software is
requested to send the modifications to the original developer so that
they can be incorporated into the canonical version. It is also
requested that these non-binding requests be included whenever the
above license is reproduced.
*/
#ifndef INCLUDED_OSCPACK_OSCOUTBOUNDPACKETSTREAM_H
#define INCLUDED_OSCPACK_OSCOUTBOUNDPACKETSTREAM_H
#include <cstring> // size_t
#include "OscTypes.h"
#include "OscException.h"
namespace osc{
class OutOfBufferMemoryException : public Exception{
public:
OutOfBufferMemoryException( const char *w="out of buffer memory" )
: Exception( w ) {}
};
class BundleNotInProgressException : public Exception{
public:
BundleNotInProgressException(
const char *w="call to EndBundle when bundle is not in progress" )
: Exception( w ) {}
};
class MessageInProgressException : public Exception{
public:
MessageInProgressException(
const char *w="opening or closing bundle or message while message is in progress" )
: Exception( w ) {}
};
class MessageNotInProgressException : public Exception{
public:
MessageNotInProgressException(
const char *w="call to EndMessage when message is not in progress" )
: Exception( w ) {}
};
class OutboundPacketStream{
public:
OutboundPacketStream( char *buffer, std::size_t capacity );
~OutboundPacketStream();
void Clear();
std::size_t Capacity() const;
// invariant: size() is valid even while building a message.
std::size_t Size() const;
const char *Data() const;
// indicates that all messages have been closed with a matching EndMessage
// and all bundles have been closed with a matching EndBundle
bool IsReady() const;
bool IsMessageInProgress() const;
bool IsBundleInProgress() const;
OutboundPacketStream& operator<<( const BundleInitiator& rhs );
OutboundPacketStream& operator<<( const BundleTerminator& rhs );
OutboundPacketStream& operator<<( const BeginMessage& rhs );
OutboundPacketStream& operator<<( const MessageTerminator& rhs );
OutboundPacketStream& operator<<( bool rhs );
OutboundPacketStream& operator<<( const NilType& rhs );
OutboundPacketStream& operator<<( const InfinitumType& rhs );
OutboundPacketStream& operator<<( int32 rhs );
#if !(defined(__x86_64__) || defined(_M_X64))
OutboundPacketStream& operator<<( int rhs )
{ *this << (int32)rhs; return *this; }
#endif
OutboundPacketStream& operator<<( float rhs );
OutboundPacketStream& operator<<( char rhs );
OutboundPacketStream& operator<<( const RgbaColor& rhs );
OutboundPacketStream& operator<<( const MidiMessage& rhs );
OutboundPacketStream& operator<<( int64 rhs );
OutboundPacketStream& operator<<( const TimeTag& rhs );
OutboundPacketStream& operator<<( double rhs );
OutboundPacketStream& operator<<( const char* rhs );
OutboundPacketStream& operator<<( const Symbol& rhs );
OutboundPacketStream& operator<<( const Blob& rhs );
OutboundPacketStream& operator<<( const ArrayInitiator& rhs );
OutboundPacketStream& operator<<( const ArrayTerminator& rhs );
private:
char *BeginElement( char *beginPtr );
void EndElement( char *endPtr );
bool ElementSizeSlotRequired() const;
void CheckForAvailableBundleSpace();
void CheckForAvailableMessageSpace( const char *addressPattern );
void CheckForAvailableArgumentSpace( std::size_t argumentLength );
char *data_;
char *end_;
char *typeTagsCurrent_; // stored in reverse order
char *messageCursor_;
char *argumentCurrent_;
// elementSizePtr_ has two special values: 0 indicates that a bundle
// isn't open, and elementSizePtr_==data_ indicates that a bundle is
// open but that it doesn't have a size slot (ie the outermost bundle)
uint32 *elementSizePtr_;
bool messageIsInProgress_;
};
} // namespace osc
#endif /* INCLUDED_OSCPACK_OSCOUTBOUNDPACKETSTREAM_H */

View File

@@ -0,0 +1,79 @@
/*
oscpack -- Open Sound Control (OSC) packet manipulation library
http://www.rossbencina.com/code/oscpack
Copyright (c) 2004-2013 Ross Bencina <rossb@audiomulch.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
The text above constitutes the entire oscpack license; however,
the oscpack developer(s) also make the following non-binding requests:
Any person wishing to distribute modifications to the Software is
requested to send the modifications to the original developer so that
they can be incorporated into the canonical version. It is also
requested that these non-binding requests be included whenever the
above license is reproduced.
*/
#ifndef INCLUDED_OSCPACK_OSCPACKETLISTENER_H
#define INCLUDED_OSCPACK_OSCPACKETLISTENER_H
#include "OscReceivedElements.h"
#include "../ip/PacketListener.h"
namespace osc{
class OscPacketListener : public PacketListener{
protected:
virtual void ProcessBundle( const osc::ReceivedBundle& b,
const IpEndpointName& remoteEndpoint )
{
// ignore bundle time tag for now
for( ReceivedBundle::const_iterator i = b.ElementsBegin();
i != b.ElementsEnd(); ++i ){
if( i->IsBundle() )
ProcessBundle( ReceivedBundle(*i), remoteEndpoint );
else
ProcessMessage( ReceivedMessage(*i), remoteEndpoint );
}
}
virtual void ProcessMessage( const osc::ReceivedMessage& m,
const IpEndpointName& remoteEndpoint ) = 0;
public:
virtual void ProcessPacket( const char *data, int size,
const IpEndpointName& remoteEndpoint )
{
osc::ReceivedPacket p( data, size );
if( p.IsBundle() )
ProcessBundle( ReceivedBundle(p), remoteEndpoint );
else
ProcessMessage( ReceivedMessage(p), remoteEndpoint );
}
};
} // namespace osc
#endif /* INCLUDED_OSCPACK_OSCPACKETLISTENER_H */

View File

@@ -0,0 +1,261 @@
/*
oscpack -- Open Sound Control (OSC) packet manipulation library
http://www.rossbencina.com/code/oscpack
Copyright (c) 2004-2013 Ross Bencina <rossb@audiomulch.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
The text above constitutes the entire oscpack license; however,
the oscpack developer(s) also make the following non-binding requests:
Any person wishing to distribute modifications to the Software is
requested to send the modifications to the original developer so that
they can be incorporated into the canonical version. It is also
requested that these non-binding requests be included whenever the
above license is reproduced.
*/
#include "OscPrintReceivedElements.h"
#include <cstring>
#include <ctime>
#include <iostream>
#include <iomanip>
#if defined(__BORLANDC__) // workaround for BCB4 release build intrinsics bug
namespace std {
using ::__strcpy__; // avoid error: E2316 '__strcpy__' is not a member of 'std'.
}
#endif
namespace osc{
std::ostream& operator<<( std::ostream & os,
const ReceivedMessageArgument& arg )
{
switch( arg.TypeTag() ){
case TRUE_TYPE_TAG:
os << "bool:true";
break;
case FALSE_TYPE_TAG:
os << "bool:false";
break;
case NIL_TYPE_TAG:
os << "(Nil)";
break;
case INFINITUM_TYPE_TAG:
os << "(Infinitum)";
break;
case INT32_TYPE_TAG:
os << "int32:" << arg.AsInt32Unchecked();
break;
case FLOAT_TYPE_TAG:
os << "float32:" << arg.AsFloatUnchecked();
break;
case CHAR_TYPE_TAG:
{
char s[2] = {0};
s[0] = arg.AsCharUnchecked();
os << "char:'" << s << "'";
}
break;
case RGBA_COLOR_TYPE_TAG:
{
uint32 color = arg.AsRgbaColorUnchecked();
os << "RGBA:0x"
<< std::hex << std::setfill('0')
<< std::setw(2) << (int)((color>>24) & 0xFF)
<< std::setw(2) << (int)((color>>16) & 0xFF)
<< std::setw(2) << (int)((color>>8) & 0xFF)
<< std::setw(2) << (int)(color & 0xFF)
<< std::setfill(' ');
os.unsetf(std::ios::basefield);
}
break;
case MIDI_MESSAGE_TYPE_TAG:
{
uint32 m = arg.AsMidiMessageUnchecked();
os << "midi (port, status, data1, data2):<<"
<< std::hex << std::setfill('0')
<< "0x" << std::setw(2) << (int)((m>>24) & 0xFF)
<< " 0x" << std::setw(2) << (int)((m>>16) & 0xFF)
<< " 0x" << std::setw(2) << (int)((m>>8) & 0xFF)
<< " 0x" << std::setw(2) << (int)(m & 0xFF)
<< std::setfill(' ') << ">>";
os.unsetf(std::ios::basefield);
}
break;
case INT64_TYPE_TAG:
os << "int64:" << arg.AsInt64Unchecked();
break;
case TIME_TAG_TYPE_TAG:
{
os << "OSC-timetag:" << arg.AsTimeTagUnchecked() << " ";
std::time_t t =
(unsigned long)( arg.AsTimeTagUnchecked() >> 32 );
const char *timeString = std::ctime( &t );
size_t len = std::strlen( timeString );
// -1 to omit trailing newline from string returned by ctime()
if( len > 1 )
os.write( timeString, len - 1 );
}
break;
case DOUBLE_TYPE_TAG:
os << "double:" << arg.AsDoubleUnchecked();
break;
case STRING_TYPE_TAG:
os << "OSC-string:`" << arg.AsStringUnchecked() << "'";
break;
case SYMBOL_TYPE_TAG:
os << "OSC-string (symbol):`" << arg.AsSymbolUnchecked() << "'";
break;
case BLOB_TYPE_TAG:
{
const void *data;
osc_bundle_element_size_t size;
arg.AsBlobUnchecked( data, size );
os << "OSC-blob:<<" << std::hex << std::setfill('0');
unsigned char *p = (unsigned char*)data;
for( osc_bundle_element_size_t i = 0; i < size; ++i ){
os << "0x" << std::setw(2) << int(p[i]);
if( i != size-1 )
os << ' ';
}
os.unsetf(std::ios::basefield);
os << ">>" << std::setfill(' ');
}
break;
case ARRAY_BEGIN_TYPE_TAG:
os << "[";
break;
case ARRAY_END_TYPE_TAG:
os << "]";
break;
default:
os << "unknown";
}
return os;
}
std::ostream& operator<<( std::ostream & os, const ReceivedMessage& m )
{
os << "[";
if( m.AddressPatternIsUInt32() )
os << m.AddressPatternAsUInt32();
else
os << m.AddressPattern();
bool first = true;
for( ReceivedMessage::const_iterator i = m.ArgumentsBegin();
i != m.ArgumentsEnd(); ++i ){
if( first ){
os << " ";
first = false;
}else{
os << ", ";
}
os << *i;
}
os << "]";
return os;
}
std::ostream& operator<<( std::ostream & os, const ReceivedBundle& b )
{
static int indent = 0;
for( int j=0; j < indent; ++j )
os << " ";
os << "{ ( ";
if( b.TimeTag() == 1 )
os << "immediate";
else
os << b.TimeTag();
os << " )\n";
++indent;
for( ReceivedBundle::const_iterator i = b.ElementsBegin();
i != b.ElementsEnd(); ++i ){
if( i->IsBundle() ){
ReceivedBundle b(*i);
os << b << "\n";
}else{
ReceivedMessage m(*i);
for( int j=0; j < indent; ++j )
os << " ";
os << m << "\n";
}
}
--indent;
for( int j=0; j < indent; ++j )
os << " ";
os << "}";
return os;
}
std::ostream& operator<<( std::ostream & os, const ReceivedPacket& p )
{
if( p.IsBundle() ){
ReceivedBundle b(p);
os << b << "\n";
}else{
ReceivedMessage m(p);
os << m << "\n";
}
return os;
}
} // namespace osc

View File

@@ -0,0 +1,54 @@
/*
oscpack -- Open Sound Control (OSC) packet manipulation library
http://www.rossbencina.com/code/oscpack
Copyright (c) 2004-2013 Ross Bencina <rossb@audiomulch.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
The text above constitutes the entire oscpack license; however,
the oscpack developer(s) also make the following non-binding requests:
Any person wishing to distribute modifications to the Software is
requested to send the modifications to the original developer so that
they can be incorporated into the canonical version. It is also
requested that these non-binding requests be included whenever the
above license is reproduced.
*/
#ifndef INCLUDED_OSCPACK_OSCPRINTRECEIVEDELEMENTS_H
#define INCLUDED_OSCPACK_OSCPRINTRECEIVEDELEMENTS_H
#include <iosfwd>
#include "OscReceivedElements.h"
namespace osc{
std::ostream& operator<<( std::ostream & os, const ReceivedPacket& p );
std::ostream& operator<<( std::ostream & os, const ReceivedMessageArgument& arg );
std::ostream& operator<<( std::ostream & os, const ReceivedMessage& m );
std::ostream& operator<<( std::ostream & os, const ReceivedBundle& b );
} // namespace osc
#endif /* INCLUDED_OSCPACK_OSCPRINTRECEIVEDELEMENTS_H */

View File

@@ -0,0 +1,796 @@
/*
oscpack -- Open Sound Control (OSC) packet manipulation library
http://www.rossbencina.com/code/oscpack
Copyright (c) 2004-2013 Ross Bencina <rossb@audiomulch.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
The text above constitutes the entire oscpack license; however,
the oscpack developer(s) also make the following non-binding requests:
Any person wishing to distribute modifications to the Software is
requested to send the modifications to the original developer so that
they can be incorporated into the canonical version. It is also
requested that these non-binding requests be included whenever the
above license is reproduced.
*/
#include "OscReceivedElements.h"
#include "OscHostEndianness.h"
#include <cstddef> // ptrdiff_t
namespace osc{
// return the first 4 byte boundary after the end of a str4
// be careful about calling this version if you don't know whether
// the string is terminated correctly.
static inline const char* FindStr4End( const char *p )
{
if( p[0] == '\0' ) // special case for SuperCollider integer address pattern
return p + 4;
p += 3;
while( *p )
p += 4;
return p + 1;
}
// return the first 4 byte boundary after the end of a str4
// returns 0 if p == end or if the string is unterminated
static inline const char* FindStr4End( const char *p, const char *end )
{
if( p >= end )
return 0;
if( p[0] == '\0' ) // special case for SuperCollider integer address pattern
return p + 4;
p += 3;
end -= 1;
while( p < end && *p )
p += 4;
if( *p )
return 0;
else
return p + 1;
}
// round up to the next highest multiple of 4. unless x is already a multiple of 4
static inline uint32 RoundUp4( uint32 x )
{
return (x + 3) & ~((uint32)0x03);
}
static inline int32 ToInt32( const char *p )
{
#ifdef OSC_HOST_LITTLE_ENDIAN
union{
osc::int32 i;
char c[4];
} u;
u.c[0] = p[3];
u.c[1] = p[2];
u.c[2] = p[1];
u.c[3] = p[0];
return u.i;
#else
return *(int32*)p;
#endif
}
static inline uint32 ToUInt32( const char *p )
{
#ifdef OSC_HOST_LITTLE_ENDIAN
union{
osc::uint32 i;
char c[4];
} u;
u.c[0] = p[3];
u.c[1] = p[2];
u.c[2] = p[1];
u.c[3] = p[0];
return u.i;
#else
return *(uint32*)p;
#endif
}
static inline int64 ToInt64( const char *p )
{
#ifdef OSC_HOST_LITTLE_ENDIAN
union{
osc::int64 i;
char c[8];
} u;
u.c[0] = p[7];
u.c[1] = p[6];
u.c[2] = p[5];
u.c[3] = p[4];
u.c[4] = p[3];
u.c[5] = p[2];
u.c[6] = p[1];
u.c[7] = p[0];
return u.i;
#else
return *(int64*)p;
#endif
}
static inline uint64 ToUInt64( const char *p )
{
#ifdef OSC_HOST_LITTLE_ENDIAN
union{
osc::uint64 i;
char c[8];
} u;
u.c[0] = p[7];
u.c[1] = p[6];
u.c[2] = p[5];
u.c[3] = p[4];
u.c[4] = p[3];
u.c[5] = p[2];
u.c[6] = p[1];
u.c[7] = p[0];
return u.i;
#else
return *(uint64*)p;
#endif
}
//------------------------------------------------------------------------------
bool ReceivedPacket::IsBundle() const
{
return (Size() > 0 && Contents()[0] == '#');
}
//------------------------------------------------------------------------------
bool ReceivedBundleElement::IsBundle() const
{
return (Size() > 0 && Contents()[0] == '#');
}
osc_bundle_element_size_t ReceivedBundleElement::Size() const
{
return ToInt32( sizePtr_ );
}
//------------------------------------------------------------------------------
bool ReceivedMessageArgument::AsBool() const
{
if( !typeTagPtr_ )
throw MissingArgumentException();
else if( *typeTagPtr_ == TRUE_TYPE_TAG )
return true;
else if( *typeTagPtr_ == FALSE_TYPE_TAG )
return false;
else
throw WrongArgumentTypeException();
}
bool ReceivedMessageArgument::AsBoolUnchecked() const
{
if( !typeTagPtr_ )
throw MissingArgumentException();
else if( *typeTagPtr_ == TRUE_TYPE_TAG )
return true;
else
return false;
}
int32 ReceivedMessageArgument::AsInt32() const
{
if( !typeTagPtr_ )
throw MissingArgumentException();
else if( *typeTagPtr_ == INT32_TYPE_TAG )
return AsInt32Unchecked();
else
throw WrongArgumentTypeException();
}
int32 ReceivedMessageArgument::AsInt32Unchecked() const
{
#ifdef OSC_HOST_LITTLE_ENDIAN
union{
osc::int32 i;
char c[4];
} u;
u.c[0] = argumentPtr_[3];
u.c[1] = argumentPtr_[2];
u.c[2] = argumentPtr_[1];
u.c[3] = argumentPtr_[0];
return u.i;
#else
return *(int32*)argument_;
#endif
}
float ReceivedMessageArgument::AsFloat() const
{
if( !typeTagPtr_ )
throw MissingArgumentException();
else if( *typeTagPtr_ == FLOAT_TYPE_TAG )
return AsFloatUnchecked();
else
throw WrongArgumentTypeException();
}
float ReceivedMessageArgument::AsFloatUnchecked() const
{
#ifdef OSC_HOST_LITTLE_ENDIAN
union{
float f;
char c[4];
} u;
u.c[0] = argumentPtr_[3];
u.c[1] = argumentPtr_[2];
u.c[2] = argumentPtr_[1];
u.c[3] = argumentPtr_[0];
return u.f;
#else
return *(float*)argument_;
#endif
}
char ReceivedMessageArgument::AsChar() const
{
if( !typeTagPtr_ )
throw MissingArgumentException();
else if( *typeTagPtr_ == CHAR_TYPE_TAG )
return AsCharUnchecked();
else
throw WrongArgumentTypeException();
}
char ReceivedMessageArgument::AsCharUnchecked() const
{
return (char)ToInt32( argumentPtr_ );
}
uint32 ReceivedMessageArgument::AsRgbaColor() const
{
if( !typeTagPtr_ )
throw MissingArgumentException();
else if( *typeTagPtr_ == RGBA_COLOR_TYPE_TAG )
return AsRgbaColorUnchecked();
else
throw WrongArgumentTypeException();
}
uint32 ReceivedMessageArgument::AsRgbaColorUnchecked() const
{
return ToUInt32( argumentPtr_ );
}
uint32 ReceivedMessageArgument::AsMidiMessage() const
{
if( !typeTagPtr_ )
throw MissingArgumentException();
else if( *typeTagPtr_ == MIDI_MESSAGE_TYPE_TAG )
return AsMidiMessageUnchecked();
else
throw WrongArgumentTypeException();
}
uint32 ReceivedMessageArgument::AsMidiMessageUnchecked() const
{
return ToUInt32( argumentPtr_ );
}
int64 ReceivedMessageArgument::AsInt64() const
{
if( !typeTagPtr_ )
throw MissingArgumentException();
else if( *typeTagPtr_ == INT64_TYPE_TAG )
return AsInt64Unchecked();
else
throw WrongArgumentTypeException();
}
int64 ReceivedMessageArgument::AsInt64Unchecked() const
{
return ToInt64( argumentPtr_ );
}
uint64 ReceivedMessageArgument::AsTimeTag() const
{
if( !typeTagPtr_ )
throw MissingArgumentException();
else if( *typeTagPtr_ == TIME_TAG_TYPE_TAG )
return AsTimeTagUnchecked();
else
throw WrongArgumentTypeException();
}
uint64 ReceivedMessageArgument::AsTimeTagUnchecked() const
{
return ToUInt64( argumentPtr_ );
}
double ReceivedMessageArgument::AsDouble() const
{
if( !typeTagPtr_ )
throw MissingArgumentException();
else if( *typeTagPtr_ == DOUBLE_TYPE_TAG )
return AsDoubleUnchecked();
else
throw WrongArgumentTypeException();
}
double ReceivedMessageArgument::AsDoubleUnchecked() const
{
#ifdef OSC_HOST_LITTLE_ENDIAN
union{
double d;
char c[8];
} u;
u.c[0] = argumentPtr_[7];
u.c[1] = argumentPtr_[6];
u.c[2] = argumentPtr_[5];
u.c[3] = argumentPtr_[4];
u.c[4] = argumentPtr_[3];
u.c[5] = argumentPtr_[2];
u.c[6] = argumentPtr_[1];
u.c[7] = argumentPtr_[0];
return u.d;
#else
return *(double*)argument_;
#endif
}
const char* ReceivedMessageArgument::AsString() const
{
if( !typeTagPtr_ )
throw MissingArgumentException();
else if( *typeTagPtr_ == STRING_TYPE_TAG )
return argumentPtr_;
else
throw WrongArgumentTypeException();
}
const char* ReceivedMessageArgument::AsSymbol() const
{
if( !typeTagPtr_ )
throw MissingArgumentException();
else if( *typeTagPtr_ == SYMBOL_TYPE_TAG )
return argumentPtr_;
else
throw WrongArgumentTypeException();
}
void ReceivedMessageArgument::AsBlob( const void*& data, osc_bundle_element_size_t& size ) const
{
if( !typeTagPtr_ )
throw MissingArgumentException();
else if( *typeTagPtr_ == BLOB_TYPE_TAG )
AsBlobUnchecked( data, size );
else
throw WrongArgumentTypeException();
}
void ReceivedMessageArgument::AsBlobUnchecked( const void*& data, osc_bundle_element_size_t& size ) const
{
// read blob size as an unsigned int then validate
osc_bundle_element_size_t sizeResult = (osc_bundle_element_size_t)ToUInt32( argumentPtr_ );
if( !IsValidElementSizeValue(sizeResult) )
throw MalformedMessageException("invalid blob size");
size = sizeResult;
data = (void*)(argumentPtr_+ osc::OSC_SIZEOF_INT32);
}
std::size_t ReceivedMessageArgument::ComputeArrayItemCount() const
{
// it is only valid to call ComputeArrayItemCount when the argument is the array start marker
if( !IsArrayBegin() )
throw WrongArgumentTypeException();
std::size_t result = 0;
unsigned int level = 0;
const char *typeTag = typeTagPtr_ + 1;
// iterate through all type tags. note that ReceivedMessage::Init
// has already checked that the message is well formed.
while( *typeTag ) {
switch( *typeTag++ ) {
case ARRAY_BEGIN_TYPE_TAG:
level += 1;
break;
case ARRAY_END_TYPE_TAG:
if(level == 0)
return result;
level -= 1;
break;
default:
if( level == 0 ) // only count items at level 0
++result;
}
}
return result;
}
//------------------------------------------------------------------------------
void ReceivedMessageArgumentIterator::Advance()
{
if( !value_.typeTagPtr_ )
return;
switch( *value_.typeTagPtr_++ ){
case '\0':
// don't advance past end
--value_.typeTagPtr_;
break;
case TRUE_TYPE_TAG:
case FALSE_TYPE_TAG:
case NIL_TYPE_TAG:
case INFINITUM_TYPE_TAG:
// zero length
break;
case INT32_TYPE_TAG:
case FLOAT_TYPE_TAG:
case CHAR_TYPE_TAG:
case RGBA_COLOR_TYPE_TAG:
case MIDI_MESSAGE_TYPE_TAG:
value_.argumentPtr_ += 4;
break;
case INT64_TYPE_TAG:
case TIME_TAG_TYPE_TAG:
case DOUBLE_TYPE_TAG:
value_.argumentPtr_ += 8;
break;
case STRING_TYPE_TAG:
case SYMBOL_TYPE_TAG:
// we use the unsafe function FindStr4End(char*) here because all of
// the arguments have already been validated in
// ReceivedMessage::Init() below.
value_.argumentPtr_ = FindStr4End( value_.argumentPtr_ );
break;
case BLOB_TYPE_TAG:
{
// treat blob size as an unsigned int for the purposes of this calculation
uint32 blobSize = ToUInt32( value_.argumentPtr_ );
value_.argumentPtr_ = value_.argumentPtr_ + osc::OSC_SIZEOF_INT32 + RoundUp4( blobSize );
}
break;
case ARRAY_BEGIN_TYPE_TAG:
case ARRAY_END_TYPE_TAG:
// [ Indicates the beginning of an array. The tags following are for
// data in the Array until a close brace tag is reached.
// ] Indicates the end of an array.
// zero length, don't advance argument ptr
break;
default: // unknown type tag
// don't advance
--value_.typeTagPtr_;
break;
}
}
//------------------------------------------------------------------------------
ReceivedMessage::ReceivedMessage( const ReceivedPacket& packet )
: addressPattern_( packet.Contents() )
{
Init( packet.Contents(), packet.Size() );
}
ReceivedMessage::ReceivedMessage( const ReceivedBundleElement& bundleElement )
: addressPattern_( bundleElement.Contents() )
{
Init( bundleElement.Contents(), bundleElement.Size() );
}
bool ReceivedMessage::AddressPatternIsUInt32() const
{
return (addressPattern_[0] == '\0');
}
uint32 ReceivedMessage::AddressPatternAsUInt32() const
{
return ToUInt32( addressPattern_ );
}
void ReceivedMessage::Init( const char *message, osc_bundle_element_size_t size )
{
if( !IsValidElementSizeValue(size) )
throw MalformedMessageException( "invalid message size" );
if( size == 0 )
throw MalformedMessageException( "zero length messages not permitted" );
if( !IsMultipleOf4(size) )
throw MalformedMessageException( "message size must be multiple of four" );
const char *end = message + size;
typeTagsBegin_ = FindStr4End( addressPattern_, end );
if( typeTagsBegin_ == 0 ){
// address pattern was not terminated before end
throw MalformedMessageException( "unterminated address pattern" );
}
if( typeTagsBegin_ == end ){
// message consists of only the address pattern - no arguments or type tags.
typeTagsBegin_ = 0;
typeTagsEnd_ = 0;
arguments_ = 0;
}else{
if( *typeTagsBegin_ != ',' )
throw MalformedMessageException( "type tags not present" );
if( *(typeTagsBegin_ + 1) == '\0' ){
// zero length type tags
typeTagsBegin_ = 0;
typeTagsEnd_ = 0;
arguments_ = 0;
}else{
// check that all arguments are present and well formed
arguments_ = FindStr4End( typeTagsBegin_, end );
if( arguments_ == 0 ){
throw MalformedMessageException( "type tags were not terminated before end of message" );
}
++typeTagsBegin_; // advance past initial ','
const char *typeTag = typeTagsBegin_;
const char *argument = arguments_;
unsigned int arrayLevel = 0;
do{
switch( *typeTag ){
case TRUE_TYPE_TAG:
case FALSE_TYPE_TAG:
case NIL_TYPE_TAG:
case INFINITUM_TYPE_TAG:
// zero length
break;
// [ Indicates the beginning of an array. The tags following are for
// data in the Array until a close brace tag is reached.
// ] Indicates the end of an array.
case ARRAY_BEGIN_TYPE_TAG:
++arrayLevel;
// (zero length argument data)
break;
case ARRAY_END_TYPE_TAG:
--arrayLevel;
// (zero length argument data)
break;
case INT32_TYPE_TAG:
case FLOAT_TYPE_TAG:
case CHAR_TYPE_TAG:
case RGBA_COLOR_TYPE_TAG:
case MIDI_MESSAGE_TYPE_TAG:
if( argument == end )
throw MalformedMessageException( "arguments exceed message size" );
argument += 4;
if( argument > end )
throw MalformedMessageException( "arguments exceed message size" );
break;
case INT64_TYPE_TAG:
case TIME_TAG_TYPE_TAG:
case DOUBLE_TYPE_TAG:
if( argument == end )
throw MalformedMessageException( "arguments exceed message size" );
argument += 8;
if( argument > end )
throw MalformedMessageException( "arguments exceed message size" );
break;
case STRING_TYPE_TAG:
case SYMBOL_TYPE_TAG:
if( argument == end )
throw MalformedMessageException( "arguments exceed message size" );
argument = FindStr4End( argument, end );
if( argument == 0 )
throw MalformedMessageException( "unterminated string argument" );
break;
case BLOB_TYPE_TAG:
{
if( argument + osc::OSC_SIZEOF_INT32 > end )
MalformedMessageException( "arguments exceed message size" );
// treat blob size as an unsigned int for the purposes of this calculation
uint32 blobSize = ToUInt32( argument );
argument = argument + osc::OSC_SIZEOF_INT32 + RoundUp4( blobSize );
if( argument > end )
MalformedMessageException( "arguments exceed message size" );
}
break;
default:
throw MalformedMessageException( "unknown type tag" );
}
}while( *++typeTag != '\0' );
typeTagsEnd_ = typeTag;
if( arrayLevel != 0 )
throw MalformedMessageException( "array was not terminated before end of message (expected ']' end of array tag)" );
}
// These invariants should be guaranteed by the above code.
// we depend on them in the implementation of ArgumentCount()
#ifndef NDEBUG
std::ptrdiff_t argumentCount = typeTagsEnd_ - typeTagsBegin_;
assert( argumentCount >= 0 );
assert( argumentCount <= OSC_INT32_MAX );
#endif
}
}
//------------------------------------------------------------------------------
ReceivedBundle::ReceivedBundle( const ReceivedPacket& packet )
: elementCount_( 0 )
{
Init( packet.Contents(), packet.Size() );
}
ReceivedBundle::ReceivedBundle( const ReceivedBundleElement& bundleElement )
: elementCount_( 0 )
{
Init( bundleElement.Contents(), bundleElement.Size() );
}
void ReceivedBundle::Init( const char *bundle, osc_bundle_element_size_t size )
{
if( !IsValidElementSizeValue(size) )
throw MalformedBundleException( "invalid bundle size" );
if( size < 16 )
throw MalformedBundleException( "packet too short for bundle" );
if( !IsMultipleOf4(size) )
throw MalformedBundleException( "bundle size must be multiple of four" );
if( bundle[0] != '#'
|| bundle[1] != 'b'
|| bundle[2] != 'u'
|| bundle[3] != 'n'
|| bundle[4] != 'd'
|| bundle[5] != 'l'
|| bundle[6] != 'e'
|| bundle[7] != '\0' )
throw MalformedBundleException( "bad bundle address pattern" );
end_ = bundle + size;
timeTag_ = bundle + 8;
const char *p = timeTag_ + 8;
while( p < end_ ){
if( p + osc::OSC_SIZEOF_INT32 > end_ )
throw MalformedBundleException( "packet too short for elementSize" );
// treat element size as an unsigned int for the purposes of this calculation
uint32 elementSize = ToUInt32( p );
if( (elementSize & ((uint32)0x03)) != 0 )
throw MalformedBundleException( "bundle element size must be multiple of four" );
p += osc::OSC_SIZEOF_INT32 + elementSize;
if( p > end_ )
throw MalformedBundleException( "packet too short for bundle element" );
++elementCount_;
}
if( p != end_ )
throw MalformedBundleException( "bundle contents " );
}
uint64 ReceivedBundle::TimeTag() const
{
return ToUInt64( timeTag_ );
}
} // namespace osc

View File

@@ -0,0 +1,548 @@
/*
oscpack -- Open Sound Control (OSC) packet manipulation library
http://www.rossbencina.com/code/oscpack
Copyright (c) 2004-2013 Ross Bencina <rossb@audiomulch.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
The text above constitutes the entire oscpack license; however,
the oscpack developer(s) also make the following non-binding requests:
Any person wishing to distribute modifications to the Software is
requested to send the modifications to the original developer so that
they can be incorporated into the canonical version. It is also
requested that these non-binding requests be included whenever the
above license is reproduced.
*/
#ifndef INCLUDED_OSCPACK_OSCRECEIVEDELEMENTS_H
#define INCLUDED_OSCPACK_OSCRECEIVEDELEMENTS_H
#include <cassert>
#include <cstddef>
#include <cstring> // size_t
#include "OscTypes.h"
#include "OscException.h"
namespace osc{
class MalformedPacketException : public Exception{
public:
MalformedPacketException( const char *w="malformed packet" )
: Exception( w ) {}
};
class MalformedMessageException : public Exception{
public:
MalformedMessageException( const char *w="malformed message" )
: Exception( w ) {}
};
class MalformedBundleException : public Exception{
public:
MalformedBundleException( const char *w="malformed bundle" )
: Exception( w ) {}
};
class WrongArgumentTypeException : public Exception{
public:
WrongArgumentTypeException( const char *w="wrong argument type" )
: Exception( w ) {}
};
class MissingArgumentException : public Exception{
public:
MissingArgumentException( const char *w="missing argument" )
: Exception( w ) {}
};
class ExcessArgumentException : public Exception{
public:
ExcessArgumentException( const char *w="too many arguments" )
: Exception( w ) {}
};
class ReceivedPacket{
public:
// Although the OSC spec is not entirely clear on this, we only support
// packets up to 0x7FFFFFFC bytes long (the maximum 4-byte aligned value
// representable by an int32). An exception will be raised if you pass a
// larger value to the ReceivedPacket() constructor.
ReceivedPacket( const char *contents, osc_bundle_element_size_t size )
: contents_( contents )
, size_( ValidateSize(size) ) {}
ReceivedPacket( const char *contents, std::size_t size )
: contents_( contents )
, size_( ValidateSize( (osc_bundle_element_size_t)size ) ) {}
#if !(defined(__x86_64__) || defined(_M_X64))
ReceivedPacket( const char *contents, int size )
: contents_( contents )
, size_( ValidateSize( (osc_bundle_element_size_t)size ) ) {}
#endif
bool IsMessage() const { return !IsBundle(); }
bool IsBundle() const;
osc_bundle_element_size_t Size() const { return size_; }
const char *Contents() const { return contents_; }
private:
const char *contents_;
osc_bundle_element_size_t size_;
static osc_bundle_element_size_t ValidateSize( osc_bundle_element_size_t size )
{
// sanity check integer types declared in OscTypes.h
// you'll need to fix OscTypes.h if any of these asserts fail
assert( sizeof(osc::int32) == 4 );
assert( sizeof(osc::uint32) == 4 );
assert( sizeof(osc::int64) == 8 );
assert( sizeof(osc::uint64) == 8 );
if( !IsValidElementSizeValue(size) )
throw MalformedPacketException( "invalid packet size" );
if( size == 0 )
throw MalformedPacketException( "zero length elements not permitted" );
if( !IsMultipleOf4(size) )
throw MalformedPacketException( "element size must be multiple of four" );
return size;
}
};
class ReceivedBundleElement{
public:
ReceivedBundleElement( const char *sizePtr )
: sizePtr_( sizePtr ) {}
friend class ReceivedBundleElementIterator;
bool IsMessage() const { return !IsBundle(); }
bool IsBundle() const;
osc_bundle_element_size_t Size() const;
const char *Contents() const { return sizePtr_ + osc::OSC_SIZEOF_INT32; }
private:
const char *sizePtr_;
};
class ReceivedBundleElementIterator{
public:
ReceivedBundleElementIterator( const char *sizePtr )
: value_( sizePtr ) {}
ReceivedBundleElementIterator operator++()
{
Advance();
return *this;
}
ReceivedBundleElementIterator operator++(int)
{
ReceivedBundleElementIterator old( *this );
Advance();
return old;
}
const ReceivedBundleElement& operator*() const { return value_; }
const ReceivedBundleElement* operator->() const { return &value_; }
friend bool operator==(const ReceivedBundleElementIterator& lhs,
const ReceivedBundleElementIterator& rhs );
private:
ReceivedBundleElement value_;
void Advance() { value_.sizePtr_ = value_.Contents() + value_.Size(); }
bool IsEqualTo( const ReceivedBundleElementIterator& rhs ) const
{
return value_.sizePtr_ == rhs.value_.sizePtr_;
}
};
inline bool operator==(const ReceivedBundleElementIterator& lhs,
const ReceivedBundleElementIterator& rhs )
{
return lhs.IsEqualTo( rhs );
}
inline bool operator!=(const ReceivedBundleElementIterator& lhs,
const ReceivedBundleElementIterator& rhs )
{
return !( lhs == rhs );
}
class ReceivedMessageArgument{
public:
ReceivedMessageArgument( const char *typeTagPtr, const char *argumentPtr )
: typeTagPtr_( typeTagPtr )
, argumentPtr_( argumentPtr ) {}
friend class ReceivedMessageArgumentIterator;
char TypeTag() const { return *typeTagPtr_; }
// the unchecked methods below don't check whether the argument actually
// is of the specified type. they should only be used if you've already
// checked the type tag or the associated IsType() method.
bool IsBool() const
{ return *typeTagPtr_ == TRUE_TYPE_TAG || *typeTagPtr_ == FALSE_TYPE_TAG; }
bool AsBool() const;
bool AsBoolUnchecked() const;
bool IsNil() const { return *typeTagPtr_ == NIL_TYPE_TAG; }
bool IsInfinitum() const { return *typeTagPtr_ == INFINITUM_TYPE_TAG; }
bool IsInt32() const { return *typeTagPtr_ == INT32_TYPE_TAG; }
int32 AsInt32() const;
int32 AsInt32Unchecked() const;
bool IsFloat() const { return *typeTagPtr_ == FLOAT_TYPE_TAG; }
float AsFloat() const;
float AsFloatUnchecked() const;
bool IsChar() const { return *typeTagPtr_ == CHAR_TYPE_TAG; }
char AsChar() const;
char AsCharUnchecked() const;
bool IsRgbaColor() const { return *typeTagPtr_ == RGBA_COLOR_TYPE_TAG; }
uint32 AsRgbaColor() const;
uint32 AsRgbaColorUnchecked() const;
bool IsMidiMessage() const { return *typeTagPtr_ == MIDI_MESSAGE_TYPE_TAG; }
uint32 AsMidiMessage() const;
uint32 AsMidiMessageUnchecked() const;
bool IsInt64() const { return *typeTagPtr_ == INT64_TYPE_TAG; }
int64 AsInt64() const;
int64 AsInt64Unchecked() const;
bool IsTimeTag() const { return *typeTagPtr_ == TIME_TAG_TYPE_TAG; }
uint64 AsTimeTag() const;
uint64 AsTimeTagUnchecked() const;
bool IsDouble() const { return *typeTagPtr_ == DOUBLE_TYPE_TAG; }
double AsDouble() const;
double AsDoubleUnchecked() const;
bool IsString() const { return *typeTagPtr_ == STRING_TYPE_TAG; }
const char* AsString() const;
const char* AsStringUnchecked() const { return argumentPtr_; }
bool IsSymbol() const { return *typeTagPtr_ == SYMBOL_TYPE_TAG; }
const char* AsSymbol() const;
const char* AsSymbolUnchecked() const { return argumentPtr_; }
bool IsBlob() const { return *typeTagPtr_ == BLOB_TYPE_TAG; }
void AsBlob( const void*& data, osc_bundle_element_size_t& size ) const;
void AsBlobUnchecked( const void*& data, osc_bundle_element_size_t& size ) const;
bool IsArrayBegin() const { return *typeTagPtr_ == ARRAY_BEGIN_TYPE_TAG; }
bool IsArrayEnd() const { return *typeTagPtr_ == ARRAY_END_TYPE_TAG; }
// Calculate the number of top-level items in the array. Nested arrays count as one item.
// Only valid at array start. Will throw an exception if IsArrayStart() == false.
std::size_t ComputeArrayItemCount() const;
private:
const char *typeTagPtr_;
const char *argumentPtr_;
};
class ReceivedMessageArgumentIterator{
public:
ReceivedMessageArgumentIterator( const char *typeTags, const char *arguments )
: value_( typeTags, arguments ) {}
ReceivedMessageArgumentIterator operator++()
{
Advance();
return *this;
}
ReceivedMessageArgumentIterator operator++(int)
{
ReceivedMessageArgumentIterator old( *this );
Advance();
return old;
}
const ReceivedMessageArgument& operator*() const { return value_; }
const ReceivedMessageArgument* operator->() const { return &value_; }
friend bool operator==(const ReceivedMessageArgumentIterator& lhs,
const ReceivedMessageArgumentIterator& rhs );
private:
ReceivedMessageArgument value_;
void Advance();
bool IsEqualTo( const ReceivedMessageArgumentIterator& rhs ) const
{
return value_.typeTagPtr_ == rhs.value_.typeTagPtr_;
}
};
inline bool operator==(const ReceivedMessageArgumentIterator& lhs,
const ReceivedMessageArgumentIterator& rhs )
{
return lhs.IsEqualTo( rhs );
}
inline bool operator!=(const ReceivedMessageArgumentIterator& lhs,
const ReceivedMessageArgumentIterator& rhs )
{
return !( lhs == rhs );
}
class ReceivedMessageArgumentStream{
friend class ReceivedMessage;
ReceivedMessageArgumentStream( const ReceivedMessageArgumentIterator& begin,
const ReceivedMessageArgumentIterator& end )
: p_( begin )
, end_( end ) {}
ReceivedMessageArgumentIterator p_, end_;
public:
// end of stream
bool Eos() const { return p_ == end_; }
ReceivedMessageArgumentStream& operator>>( bool& rhs )
{
if( Eos() )
throw MissingArgumentException();
rhs = (*p_++).AsBool();
return *this;
}
// not sure if it would be useful to stream Nil and Infinitum
// for now it's not possible
// same goes for array boundaries
ReceivedMessageArgumentStream& operator>>( int32& rhs )
{
if( Eos() )
throw MissingArgumentException();
rhs = (*p_++).AsInt32();
return *this;
}
ReceivedMessageArgumentStream& operator>>( float& rhs )
{
if( Eos() )
throw MissingArgumentException();
rhs = (*p_++).AsFloat();
return *this;
}
ReceivedMessageArgumentStream& operator>>( char& rhs )
{
if( Eos() )
throw MissingArgumentException();
rhs = (*p_++).AsChar();
return *this;
}
ReceivedMessageArgumentStream& operator>>( RgbaColor& rhs )
{
if( Eos() )
throw MissingArgumentException();
rhs.value = (*p_++).AsRgbaColor();
return *this;
}
ReceivedMessageArgumentStream& operator>>( MidiMessage& rhs )
{
if( Eos() )
throw MissingArgumentException();
rhs.value = (*p_++).AsMidiMessage();
return *this;
}
ReceivedMessageArgumentStream& operator>>( int64& rhs )
{
if( Eos() )
throw MissingArgumentException();
rhs = (*p_++).AsInt64();
return *this;
}
ReceivedMessageArgumentStream& operator>>( TimeTag& rhs )
{
if( Eos() )
throw MissingArgumentException();
rhs.value = (*p_++).AsTimeTag();
return *this;
}
ReceivedMessageArgumentStream& operator>>( double& rhs )
{
if( Eos() )
throw MissingArgumentException();
rhs = (*p_++).AsDouble();
return *this;
}
ReceivedMessageArgumentStream& operator>>( Blob& rhs )
{
if( Eos() )
throw MissingArgumentException();
(*p_++).AsBlob( rhs.data, rhs.size );
return *this;
}
ReceivedMessageArgumentStream& operator>>( const char*& rhs )
{
if( Eos() )
throw MissingArgumentException();
rhs = (*p_++).AsString();
return *this;
}
ReceivedMessageArgumentStream& operator>>( Symbol& rhs )
{
if( Eos() )
throw MissingArgumentException();
rhs.value = (*p_++).AsSymbol();
return *this;
}
ReceivedMessageArgumentStream& operator>>( MessageTerminator& rhs )
{
(void) rhs; // suppress unused parameter warning
if( !Eos() )
throw ExcessArgumentException();
return *this;
}
};
class ReceivedMessage{
void Init( const char *bundle, osc_bundle_element_size_t size );
public:
explicit ReceivedMessage( const ReceivedPacket& packet );
explicit ReceivedMessage( const ReceivedBundleElement& bundleElement );
const char *AddressPattern() const { return addressPattern_; }
// Support for non-standard SuperCollider integer address patterns:
bool AddressPatternIsUInt32() const;
uint32 AddressPatternAsUInt32() const;
uint32 ArgumentCount() const { return static_cast<uint32>(typeTagsEnd_ - typeTagsBegin_); }
const char *TypeTags() const { return typeTagsBegin_; }
typedef ReceivedMessageArgumentIterator const_iterator;
ReceivedMessageArgumentIterator ArgumentsBegin() const
{
return ReceivedMessageArgumentIterator( typeTagsBegin_, arguments_ );
}
ReceivedMessageArgumentIterator ArgumentsEnd() const
{
return ReceivedMessageArgumentIterator( typeTagsEnd_, 0 );
}
ReceivedMessageArgumentStream ArgumentStream() const
{
return ReceivedMessageArgumentStream( ArgumentsBegin(), ArgumentsEnd() );
}
private:
const char *addressPattern_;
const char *typeTagsBegin_;
const char *typeTagsEnd_;
const char *arguments_;
};
class ReceivedBundle{
void Init( const char *message, osc_bundle_element_size_t size );
public:
explicit ReceivedBundle( const ReceivedPacket& packet );
explicit ReceivedBundle( const ReceivedBundleElement& bundleElement );
uint64 TimeTag() const;
uint32 ElementCount() const { return elementCount_; }
typedef ReceivedBundleElementIterator const_iterator;
ReceivedBundleElementIterator ElementsBegin() const
{
return ReceivedBundleElementIterator( timeTag_ + 8 );
}
ReceivedBundleElementIterator ElementsEnd() const
{
return ReceivedBundleElementIterator( end_ );
}
private:
const char *timeTag_;
const char *end_;
uint32 elementCount_;
};
} // namespace osc
#endif /* INCLUDED_OSCPACK_OSCRECEIVEDELEMENTS_H */

View File

@@ -0,0 +1,52 @@
/*
oscpack -- Open Sound Control (OSC) packet manipulation library
http://www.rossbencina.com/code/oscpack
Copyright (c) 2004-2013 Ross Bencina <rossb@audiomulch.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
The text above constitutes the entire oscpack license; however,
the oscpack developer(s) also make the following non-binding requests:
Any person wishing to distribute modifications to the Software is
requested to send the modifications to the original developer so that
they can be incorporated into the canonical version. It is also
requested that these non-binding requests be included whenever the
above license is reproduced.
*/
#include "OscTypes.h"
namespace osc{
BundleInitiator BeginBundleImmediate(1);
BundleTerminator EndBundle;
MessageTerminator EndMessage;
NilType OscNil;
#ifndef _OBJC_OBJC_H_
NilType Nil; // Objective-C defines Nil. so our Nil is deprecated. use OscNil instead
#endif
InfinitumType Infinitum;
ArrayInitiator BeginArray;
ArrayTerminator EndArray;
} // namespace osc

240
ext/OSCPack/osc/OscTypes.h Normal file
View File

@@ -0,0 +1,240 @@
/*
oscpack -- Open Sound Control (OSC) packet manipulation library
http://www.rossbencina.com/code/oscpack
Copyright (c) 2004-2013 Ross Bencina <rossb@audiomulch.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
The text above constitutes the entire oscpack license; however,
the oscpack developer(s) also make the following non-binding requests:
Any person wishing to distribute modifications to the Software is
requested to send the modifications to the original developer so that
they can be incorporated into the canonical version. It is also
requested that these non-binding requests be included whenever the
above license is reproduced.
*/
#ifndef INCLUDED_OSCPACK_OSCTYPES_H
#define INCLUDED_OSCPACK_OSCTYPES_H
namespace osc{
// basic types
#if defined(__BORLANDC__) || defined(_MSC_VER)
typedef __int64 int64;
typedef unsigned __int64 uint64;
#elif defined(__x86_64__) || defined(_M_X64)
typedef long int64;
typedef unsigned long uint64;
#else
typedef long long int64;
typedef unsigned long long uint64;
#endif
#if defined(__x86_64__) || defined(_M_X64)
typedef signed int int32;
typedef unsigned int uint32;
#else
typedef signed long int32;
typedef unsigned long uint32;
#endif
enum ValueTypeSizes{
OSC_SIZEOF_INT32 = 4,
OSC_SIZEOF_UINT32 = 4,
OSC_SIZEOF_INT64 = 8,
OSC_SIZEOF_UINT64 = 8,
};
// osc_bundle_element_size_t is used for the size of bundle elements and blobs
// the OSC spec specifies these as int32 (signed) but we ensure that they
// are always positive since negative field sizes make no sense.
typedef int32 osc_bundle_element_size_t;
enum {
OSC_INT32_MAX = 0x7FFFFFFF,
// Element sizes are specified to be int32, and are always rounded up to nearest
// multiple of 4. Therefore their values can't be greater than 0x7FFFFFFC.
OSC_BUNDLE_ELEMENT_SIZE_MAX = 0x7FFFFFFC
};
inline bool IsValidElementSizeValue( osc_bundle_element_size_t x )
{
// sizes may not be negative or exceed OSC_BUNDLE_ELEMENT_SIZE_MAX
return x >= 0 && x <= OSC_BUNDLE_ELEMENT_SIZE_MAX;
}
inline bool IsMultipleOf4( osc_bundle_element_size_t x )
{
return (x & ((osc_bundle_element_size_t)0x03)) == 0;
}
enum TypeTagValues {
TRUE_TYPE_TAG = 'T',
FALSE_TYPE_TAG = 'F',
NIL_TYPE_TAG = 'N',
INFINITUM_TYPE_TAG = 'I',
INT32_TYPE_TAG = 'i',
FLOAT_TYPE_TAG = 'f',
CHAR_TYPE_TAG = 'c',
RGBA_COLOR_TYPE_TAG = 'r',
MIDI_MESSAGE_TYPE_TAG = 'm',
INT64_TYPE_TAG = 'h',
TIME_TAG_TYPE_TAG = 't',
DOUBLE_TYPE_TAG = 'd',
STRING_TYPE_TAG = 's',
SYMBOL_TYPE_TAG = 'S',
BLOB_TYPE_TAG = 'b',
ARRAY_BEGIN_TYPE_TAG = '[',
ARRAY_END_TYPE_TAG = ']'
};
// i/o manipulators used for streaming interfaces
struct BundleInitiator{
explicit BundleInitiator( uint64 timeTag_ ) : timeTag( timeTag_ ) {}
uint64 timeTag;
};
extern BundleInitiator BeginBundleImmediate;
inline BundleInitiator BeginBundle( uint64 timeTag=1 )
{
return BundleInitiator(timeTag);
}
struct BundleTerminator{
};
extern BundleTerminator EndBundle;
struct BeginMessage{
explicit BeginMessage( const char *addressPattern_ ) : addressPattern( addressPattern_ ) {}
const char *addressPattern;
};
struct MessageTerminator{
};
extern MessageTerminator EndMessage;
// osc specific types. they are defined as structs so they can be used
// as separately identifiable types with the streaming operators.
struct NilType{
};
extern NilType OscNil;
#ifndef _OBJC_OBJC_H_
extern NilType Nil; // Objective-C defines Nil. so our Nil is deprecated. use OscNil instead
#endif
struct InfinitumType{
};
extern InfinitumType Infinitum;
struct RgbaColor{
RgbaColor() {}
explicit RgbaColor( uint32 value_ ) : value( value_ ) {}
uint32 value;
operator uint32() const { return value; }
};
struct MidiMessage{
MidiMessage() {}
explicit MidiMessage( uint32 value_ ) : value( value_ ) {}
uint32 value;
operator uint32() const { return value; }
};
struct TimeTag{
TimeTag() {}
explicit TimeTag( uint64 value_ ) : value( value_ ) {}
uint64 value;
operator uint64() const { return value; }
};
struct Symbol{
Symbol() {}
explicit Symbol( const char* value_ ) : value( value_ ) {}
const char* value;
operator const char *() const { return value; }
};
struct Blob{
Blob() {}
explicit Blob( const void* data_, osc_bundle_element_size_t size_ )
: data( data_ ), size( size_ ) {}
const void* data;
osc_bundle_element_size_t size;
};
struct ArrayInitiator{
};
extern ArrayInitiator BeginArray;
struct ArrayTerminator{
};
extern ArrayTerminator EndArray;
} // namespace osc
#endif /* INCLUDED_OSCPACK_OSCTYPES_H */

View File

@@ -28,6 +28,16 @@
#include "Mixer.h"
#include "RenderingManager.h"
#include "UserInterfaceManager.h"
#include "Connection.h"
#if defined(APPLE)
extern "C"{
void forward_load_message(const char * filename){
Mixer::manager().load(filename);
}
}
#endif
void drawScene()
@@ -35,16 +45,35 @@ void drawScene()
Mixer::manager().draw();
}
int main(int, char *argv[])
int main(int argc, char *argv[])
{
// one extra argument is given
if (argc == 2) {
std::string argument(argv[1]);
if (argument == "--clean" || argument == "-c")
// clean start if requested : Save empty settings before loading
Settings::Save();
else {
// try to open the file
Mixer::manager().load(argument);
}
}
///
/// Settings
///
Settings::Load();
Settings::application.executable = std::string(argv[0]);
/// lock to inform an instance is running
Settings::Lock();
///
/// CONNECTION INIT
///
if ( !Connection::manager().init() )
return 1;
///
/// RENDERING INIT
///
@@ -97,6 +126,14 @@ int main(int, char *argv[])
///
Rendering::manager().terminate();
///
/// CONNECTION TERMINATE
///
Connection::manager().terminate();
/// unlock on clean exit
Settings::Unlock();
///
/// Settings
///

View File

@@ -11,4 +11,4 @@ void * get_current_nsopengl_context();
};
#endif // COCOA_TOOLKIT_H
#endif // COCOA_TOOLKIT_H

10
osx/CustomDelegate.h Normal file
View File

@@ -0,0 +1,10 @@
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface GLFWCustomDelegate : NSObject
+ (void)load; // load is called before even main() is run (as part of objc class registration)
@end
NS_ASSUME_NONNULL_END

49
osx/CustomDelegate.m Normal file
View File

@@ -0,0 +1,49 @@
#import "CustomDelegate.h"
#import <objc/runtime.h>
// part of your application
extern void forward_load_message(const char * filename);
@implementation GLFWCustomDelegate
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = objc_getClass("GLFWApplicationDelegate");
[GLFWCustomDelegate swizzle:class src:@selector(application:openFile:) tgt:@selector(swz_application:openFile:)];
[GLFWCustomDelegate swizzle:class src:@selector(application:openFiles:) tgt:@selector(swz_application:openFiles:)];
});
}
+ (void) swizzle:(Class) original_c src:(SEL)original_s tgt:(SEL)target_s{
Class target_c = [GLFWCustomDelegate class];
Method originalMethod = class_getInstanceMethod(original_c, original_s);
Method swizzledMethod = class_getInstanceMethod(target_c, target_s);
BOOL didAddMethod =
class_addMethod(original_c,
original_s,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(original_c,
target_s,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
- (BOOL)swz_application:(NSApplication *)sender openFile:(NSString *)filename{
forward_load_message(filename.UTF8String);
return YES;
}
- (void)swz_application:(NSApplication *)sender openFiles:(NSArray<NSString *> *)filenames{
forward_load_message(filenames.firstObject.UTF8String);
}
@end

View File

@@ -9,22 +9,18 @@
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>mix</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>vimix.icns</string>
<key>CFBundleTypeName</key>
<string>GLMixer Session</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>MIX </string>
</array>
<string>vimix session</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSIsAppleDefaultForType</key>
<true/>
<key>LSIsAppleDefaultForType</key>
<true/>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSItemContentTypes</key>
<array>
<string>com.bhbn.mix</string>
</array>
</dict>
</array>
<key>CFBundleExecutable</key>

442
rsc/mesh/icon_share.ply Normal file
View File

@@ -0,0 +1,442 @@
ply
format ascii 1.0
comment Created by Blender 2.90.1 - www.blender.org
element vertex 217
property float x
property float y
property float z
element face 215
property list uchar uint vertex_indices
end_header
0.031205 0.089869 0.000000
0.039943 0.089869 0.000000
0.035574 0.090163 0.000000
0.027015 0.089014 0.000000
0.044133 0.089014 0.000000
0.023043 0.087634 0.000000
0.048105 0.087634 0.000000
0.019326 0.085769 0.000000
0.051822 0.085769 0.000000
0.015903 0.083458 0.000000
0.055245 0.083458 0.000000
0.012812 0.080738 0.000000
0.058336 0.080738 0.000000
0.010092 0.077647 0.000000
0.061056 0.077647 0.000000
0.007780 0.074225 0.000000
0.063368 0.074225 0.000000
0.005916 0.070509 0.000000
0.065232 0.070509 0.000000
0.004537 0.066538 0.000000
0.066612 0.066538 0.000000
0.003681 0.062350 0.000000
0.067467 0.062350 0.000000
0.003387 0.057983 0.000000
0.067761 0.057983 0.000000
0.003389 0.057651 0.000000
0.067467 0.053617 0.000000
0.003395 0.057320 0.000000
0.003404 0.056990 0.000000
0.003416 0.056662 0.000000
0.003432 0.056334 0.000000
0.003451 0.056007 0.000000
0.003473 0.055682 0.000000
0.003499 0.055357 0.000000
0.003528 0.055033 0.000000
0.003559 0.054710 0.000000
0.003594 0.054387 0.000000
0.003631 0.054065 0.000000
0.002492 0.053467 0.000000
0.066612 0.049429 0.000000
-0.000656 0.051814 0.000000
-0.005413 0.049316 0.000000
0.065232 0.045459 0.000000
-0.011375 0.046186 0.000000
-0.018141 0.042633 0.000000
0.063368 0.041743 0.000000
-0.025309 0.038870 0.000000
0.061057 0.038321 0.000000
-0.032478 0.035106 0.000000
0.058337 0.035232 0.000000
0.014220 0.033913 0.000000
0.055247 0.032512 0.000000
-0.039244 0.031553 0.000000
0.013047 0.033297 0.000000
0.015678 0.032694 0.000000
-0.081881 0.033338 0.000000
-0.075243 0.033553 0.000000
-0.077512 0.033632 0.000000
-0.073018 0.033320 0.000000
-0.086071 0.032482 0.000000
-0.070840 0.032938 0.000000
0.009806 0.031596 0.000000
-0.068716 0.032412 0.000000
0.017207 0.031562 0.000000
0.051825 0.030200 0.000000
-0.090044 0.031103 0.000000
-0.066650 0.031748 0.000000
-0.064647 0.030951 0.000000
0.004910 0.029026 0.000000
0.018803 0.030519 0.000000
-0.045206 0.028423 0.000000
-0.093760 0.029238 0.000000
-0.062713 0.030027 0.000000
0.020463 0.029570 0.000000
0.048109 0.028335 0.000000
-0.060853 0.028980 0.000000
0.022182 0.028718 0.000000
-0.097183 0.026927 0.000000
-0.001227 0.025804 0.000000
-0.059071 0.027815 0.000000
0.023958 0.027967 0.000000
-0.049963 0.025925 0.000000
0.044137 0.026955 0.000000
0.025786 0.027322 0.000000
-0.057374 0.026540 0.000000
0.027662 0.026785 0.000000
0.039949 0.026099 0.000000
-0.100274 0.024206 0.000000
0.029584 0.026361 0.000000
-0.055765 0.025157 0.000000
0.031547 0.026054 0.000000
0.035581 0.025803 0.000000
0.033547 0.025866 0.000000
-0.053111 0.024272 0.000000
-0.008192 0.022147 0.000000
-0.054250 0.023674 0.000000
-0.102994 0.021116 0.000000
-0.015571 0.018274 0.000000
-0.105306 0.017693 0.000000
-0.022950 0.014400 0.000000
-0.107170 0.013978 0.000000
-0.029915 0.010744 0.000000
-0.108550 0.010006 0.000000
-0.036053 0.007522 0.000000
-0.109405 0.005818 0.000000
-0.040949 0.004952 0.000000
-0.109699 0.001452 0.000000
-0.044190 0.003250 0.000000
-0.045362 0.002635 0.000000
-0.045345 0.002077 0.000000
-0.045336 0.001520 0.000000
-0.045338 0.000961 0.000000
-0.109405 -0.002915 0.000000
-0.045349 0.000401 0.000000
-0.045372 -0.000164 0.000000
-0.045405 -0.000733 0.000000
-0.045449 -0.001309 0.000000
-0.045506 -0.001893 0.000000
-0.045574 -0.002485 0.000000
-0.045656 -0.003088 0.000000
-0.108550 -0.007102 0.000000
-0.045750 -0.003702 0.000000
-0.045858 -0.004329 0.000000
-0.044725 -0.004888 0.000000
-0.041592 -0.006435 0.000000
-0.036860 -0.008772 0.000000
-0.107170 -0.011074 0.000000
-0.030928 -0.011700 0.000000
-0.105306 -0.014790 0.000000
-0.024196 -0.015024 0.000000
-0.102994 -0.018212 0.000000
-0.017065 -0.018545 0.000000
-0.100274 -0.021302 0.000000
-0.009933 -0.022066 0.000000
-0.097183 -0.024023 0.000000
-0.057666 -0.023875 0.000000
-0.003202 -0.025390 0.000000
0.033232 -0.022281 0.000000
0.039943 -0.022491 0.000000
0.035574 -0.022197 0.000000
0.030935 -0.022529 0.000000
0.044133 -0.023346 0.000000
0.028689 -0.022935 0.000000
0.026500 -0.023493 0.000000
0.048105 -0.024725 0.000000
0.024373 -0.024199 0.000000
-0.059065 -0.024911 0.000000
-0.056465 -0.024468 0.000000
-0.093760 -0.026334 0.000000
0.022315 -0.025045 0.000000
-0.053142 -0.026108 0.000000
0.051822 -0.026589 0.000000
-0.060519 -0.025871 0.000000
0.020330 -0.026026 0.000000
0.002730 -0.028318 0.000000
-0.062025 -0.026755 0.000000
0.018426 -0.027137 0.000000
-0.048123 -0.028586 0.000000
-0.090043 -0.028199 0.000000
0.055245 -0.028899 0.000000
-0.063582 -0.027557 0.000000
0.016608 -0.028372 0.000000
-0.065186 -0.028277 0.000000
-0.086071 -0.029578 0.000000
-0.066835 -0.028909 0.000000
0.007462 -0.030655 0.000000
0.014882 -0.029725 0.000000
-0.041832 -0.031691 0.000000
0.058336 -0.031619 0.000000
-0.068526 -0.029453 0.000000
-0.070255 -0.029904 0.000000
-0.081881 -0.030434 0.000000
0.013253 -0.031190 0.000000
-0.072022 -0.030260 0.000000
-0.073822 -0.030518 0.000000
-0.077512 -0.030728 0.000000
-0.075653 -0.030675 0.000000
0.010595 -0.032202 0.000000
0.011728 -0.032761 0.000000
0.061056 -0.034708 0.000000
-0.034692 -0.035215 0.000000
0.063368 -0.038130 0.000000
-0.027128 -0.038949 0.000000
0.065232 -0.041845 0.000000
-0.019565 -0.042682 0.000000
0.066612 -0.045816 0.000000
-0.012425 -0.046206 0.000000
0.067467 -0.050003 0.000000
-0.006134 -0.049312 0.000000
-0.001115 -0.051789 0.000000
0.067761 -0.054370 0.000000
0.002208 -0.053429 0.000000
0.003410 -0.054022 0.000000
0.003402 -0.054370 0.000000
0.003696 -0.058736 0.000000
0.067467 -0.058736 0.000000
0.066613 -0.062924 0.000000
0.004551 -0.062924 0.000000
0.065235 -0.066895 0.000000
0.005931 -0.066895 0.000000
0.063372 -0.070611 0.000000
0.007795 -0.070611 0.000000
0.061062 -0.074033 0.000000
0.010107 -0.074033 0.000000
0.058343 -0.077124 0.000000
0.012827 -0.077124 0.000000
0.055255 -0.079844 0.000000
0.015918 -0.079844 0.000000
0.051833 -0.082156 0.000000
0.019341 -0.082156 0.000000
0.048118 -0.084021 0.000000
0.023057 -0.084021 0.000000
0.044147 -0.085400 0.000000
0.027030 -0.085400 0.000000
0.039958 -0.086256 0.000000
0.031220 -0.086256 0.000000
0.035589 -0.086549 0.000000
3 0 1 2
3 3 1 0
3 3 4 1
3 5 4 3
3 5 6 4
3 7 6 5
3 7 8 6
3 9 8 7
3 9 10 8
3 11 10 9
3 11 12 10
3 13 12 11
3 13 14 12
3 15 14 13
3 15 16 14
3 17 16 15
3 17 18 16
3 19 18 17
3 19 20 18
3 21 20 19
3 21 22 20
3 23 22 21
3 23 24 22
3 25 24 23
3 25 26 24
3 27 26 25
3 28 26 27
3 29 26 28
3 30 26 29
3 31 26 30
3 32 26 31
3 33 26 32
3 34 26 33
3 35 26 34
3 36 26 35
3 37 26 36
3 38 26 37
3 38 39 26
3 40 39 38
3 41 39 40
3 41 42 39
3 43 42 41
3 44 42 43
3 44 45 42
3 46 45 44
3 46 47 45
3 48 47 46
3 48 49 47
3 48 50 49
3 50 51 49
3 52 50 48
3 52 53 50
3 54 51 50
3 55 56 57
3 55 58 56
3 59 58 55
3 59 60 58
3 52 61 53
3 59 62 60
3 63 51 54
3 63 64 51
3 65 62 59
3 65 66 62
3 65 67 66
3 52 68 61
3 69 64 63
3 70 68 52
3 71 67 65
3 71 72 67
3 73 64 69
3 73 74 64
3 71 75 72
3 76 74 73
3 77 75 71
3 70 78 68
3 77 79 75
3 80 74 76
3 81 78 70
3 80 82 74
3 83 82 80
3 77 84 79
3 85 82 83
3 85 86 82
3 87 84 77
3 88 86 85
3 87 89 84
3 90 86 88
3 90 91 86
3 92 91 90
3 93 78 81
3 93 94 78
3 87 95 89
3 95 94 93
3 96 95 87
3 96 94 95
3 96 97 94
3 98 97 96
3 98 99 97
3 100 99 98
3 100 101 99
3 102 101 100
3 102 103 101
3 104 103 102
3 104 105 103
3 106 105 104
3 106 107 105
3 106 108 107
3 106 109 108
3 106 110 109
3 106 111 110
3 112 111 106
3 112 113 111
3 112 114 113
3 112 115 114
3 112 116 115
3 112 117 116
3 112 118 117
3 112 119 118
3 120 119 112
3 120 121 119
3 120 122 121
3 120 123 122
3 120 124 123
3 120 125 124
3 126 125 120
3 126 127 125
3 128 127 126
3 128 129 127
3 130 129 128
3 130 131 129
3 132 131 130
3 132 133 131
3 134 133 132
3 134 135 133
3 135 136 133
3 137 138 139
3 140 138 137
3 140 141 138
3 142 141 140
3 143 141 142
3 143 144 141
3 145 144 143
3 134 146 135
3 147 136 135
3 148 146 134
3 149 144 145
3 150 136 147
3 149 151 144
3 148 152 146
3 153 151 149
3 150 154 136
3 148 155 152
3 156 151 153
3 157 154 150
3 158 155 148
3 156 159 151
3 158 160 155
3 161 159 156
3 158 162 160
3 163 162 158
3 163 164 162
3 157 165 154
3 166 159 161
3 167 165 157
3 166 168 159
3 163 169 164
3 163 170 169
3 171 170 163
3 172 168 166
3 171 173 170
3 171 174 173
3 175 174 171
3 175 176 174
3 167 177 165
3 178 168 172
3 178 179 168
3 180 177 167
3 180 178 177
3 180 179 178
3 180 181 179
3 182 181 180
3 182 183 181
3 184 183 182
3 184 185 183
3 186 185 184
3 186 187 185
3 188 187 186
3 189 187 188
3 189 190 187
3 191 190 189
3 192 190 191
3 193 190 192
3 194 190 193
3 194 195 190
3 194 196 195
3 197 196 194
3 197 198 196
3 199 198 197
3 199 200 198
3 201 200 199
3 201 202 200
3 203 202 201
3 203 204 202
3 205 204 203
3 205 206 204
3 207 206 205
3 207 208 206
3 209 208 207
3 209 210 208
3 211 210 209
3 211 212 210
3 213 212 211
3 213 214 212
3 215 214 213
3 215 216 214