mirror of
https://github.com/brunoherbelin/vimix.git
synced 2025-12-08 08:50:00 +01:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce5369a0ef | ||
|
|
ec797f8d67 | ||
|
|
ce7f30fa63 | ||
|
|
79482d3d1b | ||
|
|
93e7027f48 | ||
|
|
34580ab5ea | ||
|
|
bab0e9b710 | ||
|
|
88d4e3d9d5 | ||
|
|
47c338341d | ||
|
|
3cae0cd66f | ||
|
|
0738c25fb4 | ||
|
|
b8ebab5766 | ||
|
|
954b35032a | ||
|
|
46b9a8f663 | ||
|
|
41f87aa927 | ||
|
|
05a4ac164e | ||
|
|
44901b1e78 | ||
|
|
8ef79a6dbd | ||
|
|
940dd0f2a5 | ||
|
|
4fa7e06e19 | ||
|
|
7f2c3d531c | ||
|
|
a4621f31e3 | ||
|
|
7438b257ae | ||
|
|
cb6a0aefa4 | ||
|
|
7fba62bc49 | ||
|
|
01410a59cf | ||
|
|
e60c7a4cad | ||
|
|
8fa14bda1a | ||
|
|
469ee4c26a | ||
|
|
2627174fc0 | ||
|
|
7246dfa08e | ||
|
|
db0892d25b | ||
|
|
509416d5a0 | ||
|
|
43f444f07b | ||
|
|
bbeb99056a | ||
|
|
65aefc9fb8 | ||
|
|
27239b7513 | ||
|
|
15285ec151 | ||
|
|
d7893be541 | ||
|
|
59c07ceb96 | ||
|
|
007d876dbc | ||
|
|
3a41e59f00 | ||
|
|
3a34da9322 | ||
|
|
b3ee400b1a |
@@ -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
292
Connection.cpp
Normal file
@@ -0,0 +1,292 @@
|
||||
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
#include "osc/OscOutboundPacketStream.h"
|
||||
|
||||
#include "defines.h"
|
||||
#include "Connection.h"
|
||||
#include "Settings.h"
|
||||
#include "Streamer.h"
|
||||
#include "Log.h"
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define CONNECTION_DEBUG
|
||||
#endif
|
||||
|
||||
|
||||
Connection::Connection()
|
||||
{
|
||||
receiver_ = nullptr;
|
||||
}
|
||||
|
||||
|
||||
Connection::~Connection()
|
||||
{
|
||||
if (receiver_!=nullptr) {
|
||||
receiver_->Break();
|
||||
delete receiver_;
|
||||
}
|
||||
}
|
||||
|
||||
bool Connection::init()
|
||||
{
|
||||
// add default info for myself
|
||||
connections_.push_back(ConnectionInfo());
|
||||
|
||||
// try to open a socket at base handshake port
|
||||
int trial = 0;
|
||||
while (trial < MAX_HANDSHAKE) {
|
||||
try {
|
||||
// increment the port to have unique ports
|
||||
connections_[0].port_handshake = HANDSHAKE_PORT + trial;
|
||||
connections_[0].port_stream_request = STREAM_REQUEST_PORT + trial;
|
||||
connections_[0].port_osc = OSC_DIALOG_PORT + trial;
|
||||
|
||||
// try to create listenning socket
|
||||
// through exception runtime if fails
|
||||
receiver_ = new UdpListeningReceiveSocket( IpEndpointName( IpEndpointName::ANY_ADDRESS,
|
||||
connections_[0].port_handshake ), &listener_ );
|
||||
// validate hostname
|
||||
connections_[0].name = APP_NAME "@" + NetworkToolkit::hostname() +
|
||||
"." + std::to_string(connections_[0].port_handshake-HANDSHAKE_PORT);
|
||||
// all good
|
||||
trial = MAX_HANDSHAKE;
|
||||
}
|
||||
catch (const std::runtime_error&) {
|
||||
// arg, the receiver could not be initialized
|
||||
// because the port was not available
|
||||
receiver_ = nullptr;
|
||||
}
|
||||
// try again
|
||||
trial++;
|
||||
}
|
||||
|
||||
// perfect, we could initialize the receiver
|
||||
if (receiver_!=nullptr) {
|
||||
// listen for answers
|
||||
std::thread(listen).detach();
|
||||
// regularly check for available streaming hosts
|
||||
std::thread(ask).detach();
|
||||
|
||||
// inform the application settings of our id
|
||||
Settings::application.instance_id = connections_[0].port_handshake - HANDSHAKE_PORT;
|
||||
// use or replace instance name from settings
|
||||
// if (Settings::application.instance_names.count(Settings::application.instance_id))
|
||||
// connections_[0].name = Settings::application.instance_names[Settings::application.instance_id];
|
||||
// else
|
||||
// Settings::application.instance_names[Settings::application.instance_id] = connections_[0].name;
|
||||
// restore state of Streamer
|
||||
Streaming::manager().enable( Settings::application.accept_connections );
|
||||
|
||||
}
|
||||
|
||||
return receiver_ != nullptr;
|
||||
}
|
||||
|
||||
void Connection::terminate()
|
||||
{
|
||||
if (receiver_!=nullptr)
|
||||
receiver_->AsynchronousBreak();
|
||||
|
||||
// restore state of Streamer
|
||||
Streaming::manager().enable( false );
|
||||
}
|
||||
|
||||
int Connection::numHosts () const
|
||||
{
|
||||
return connections_.size();
|
||||
}
|
||||
|
||||
ConnectionInfo Connection::info(int index)
|
||||
{
|
||||
if (connections_.empty()) {
|
||||
connections_.push_back(ConnectionInfo());
|
||||
}
|
||||
|
||||
index = CLAMP(index, 0, (int) connections_.size());
|
||||
|
||||
return connections_[index];
|
||||
}
|
||||
|
||||
|
||||
struct hasName: public std::unary_function<ConnectionInfo, bool>
|
||||
{
|
||||
inline bool operator()(const ConnectionInfo elem) const {
|
||||
return (elem.name.compare(_a) == 0);
|
||||
}
|
||||
hasName(std::string a) : _a(a) { }
|
||||
private:
|
||||
std::string _a;
|
||||
};
|
||||
|
||||
|
||||
int Connection::index(const std::string &name) const
|
||||
{
|
||||
int id = -1;
|
||||
|
||||
std::vector<ConnectionInfo>::const_iterator p = std::find_if(connections_.begin(), connections_.end(), hasName(name));
|
||||
if (p != connections_.end())
|
||||
id = std::distance(connections_.begin(), p);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
int Connection::index(ConnectionInfo i) const
|
||||
{
|
||||
int id = -1;
|
||||
|
||||
std::vector<ConnectionInfo>::const_iterator p = std::find(connections_.begin(), connections_.end(), i);
|
||||
if (p != connections_.end())
|
||||
id = std::distance(connections_.begin(), p);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
void Connection::print()
|
||||
{
|
||||
for(int i = 0; i<connections_.size(); i++) {
|
||||
Log::Info(" - %s %s:%d", connections_[i].name.c_str(), connections_[i].address.c_str(), connections_[i].port_handshake);
|
||||
}
|
||||
}
|
||||
|
||||
void Connection::listen()
|
||||
{
|
||||
#ifdef CONNECTION_DEBUG
|
||||
Log::Info("Accepting handshake on port %d", Connection::manager().connections_[0].port_handshake);
|
||||
#endif
|
||||
Connection::manager().receiver_->Run();
|
||||
}
|
||||
|
||||
void Connection::ask()
|
||||
{
|
||||
// prepare OSC PING message
|
||||
char buffer[IP_MTU_SIZE];
|
||||
osc::OutboundPacketStream p( buffer, IP_MTU_SIZE );
|
||||
p.Clear();
|
||||
p << osc::BeginMessage( OSC_PREFIX OSC_PING );
|
||||
p << Connection::manager().connections_[0].port_handshake;
|
||||
p << osc::EndMessage;
|
||||
|
||||
UdpSocket socket;
|
||||
socket.SetEnableBroadcast(true);
|
||||
|
||||
// loop infinitely
|
||||
while(true)
|
||||
{
|
||||
// broadcast on several ports
|
||||
for(int i=HANDSHAKE_PORT; i<HANDSHAKE_PORT+MAX_HANDSHAKE; i++)
|
||||
socket.SendTo( IpEndpointName( i ), p.Data(), p.Size() );
|
||||
|
||||
// wait a bit
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
|
||||
// check the list of connections for non responding (disconnected)
|
||||
std::vector< ConnectionInfo >::iterator it = Connection::manager().connections_.begin();
|
||||
for(it++; it!=Connection::manager().connections_.end(); ) {
|
||||
// decrease life score
|
||||
(*it).alive--;
|
||||
// erase connection if its life score is negative (not responding too many times)
|
||||
if ( (*it).alive < 0 ) {
|
||||
// inform streamer to cancel streaming to this client
|
||||
Streaming::manager().removeStreams( (*it).name );
|
||||
// remove from list
|
||||
it = Connection::manager().connections_.erase(it);
|
||||
#ifdef CONNECTION_DEBUG
|
||||
Log::Info("List of connection updated:");
|
||||
Connection::manager().print();
|
||||
#endif
|
||||
}
|
||||
// loop
|
||||
else
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ConnectionRequestListener::ProcessMessage( const osc::ReceivedMessage& m,
|
||||
const IpEndpointName& remoteEndpoint )
|
||||
{
|
||||
char sender[IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH];
|
||||
remoteEndpoint.AddressAndPortAsString(sender);
|
||||
|
||||
// get ip of connection (without port)
|
||||
std::string remote_ip(sender);
|
||||
remote_ip = remote_ip.substr(0, remote_ip.find_last_of(":"));
|
||||
|
||||
|
||||
try{
|
||||
// ping request : reply with pong
|
||||
if( std::strcmp( m.AddressPattern(), OSC_PREFIX OSC_PING) == 0 ){
|
||||
|
||||
// PING message has parameter : port where to reply
|
||||
osc::ReceivedMessage::const_iterator arg = m.ArgumentsBegin();
|
||||
int remote_port = (arg++)->AsInt32();
|
||||
|
||||
// ignore requests from myself
|
||||
if ( !NetworkToolkit::is_host_ip(remote_ip)
|
||||
|| Connection::manager().connections_[0].port_handshake != remote_port) {
|
||||
|
||||
// build message
|
||||
char buffer[IP_MTU_SIZE];
|
||||
osc::OutboundPacketStream p( buffer, IP_MTU_SIZE );
|
||||
p.Clear();
|
||||
p << osc::BeginMessage( OSC_PREFIX OSC_PONG );
|
||||
p << Connection::manager().connections_[0].name.c_str();
|
||||
p << Connection::manager().connections_[0].port_handshake;
|
||||
p << Connection::manager().connections_[0].port_stream_request;
|
||||
p << Connection::manager().connections_[0].port_osc;
|
||||
p << osc::EndMessage;
|
||||
|
||||
// send OSC message to port indicated by remote
|
||||
IpEndpointName host( remote_ip.c_str(), remote_port );
|
||||
UdpTransmitSocket socket( host );
|
||||
socket.Send( p.Data(), p.Size() );
|
||||
|
||||
}
|
||||
}
|
||||
// pong response: add info
|
||||
else if( std::strcmp( m.AddressPattern(), OSC_PREFIX OSC_PONG) == 0 ){
|
||||
|
||||
// create info struct
|
||||
ConnectionInfo info;
|
||||
info.address = remote_ip;
|
||||
|
||||
// add all ports info
|
||||
osc::ReceivedMessage::const_iterator arg = m.ArgumentsBegin();
|
||||
info.name = std::string( (arg++)->AsString() );
|
||||
info.port_handshake = (arg++)->AsInt32();
|
||||
info.port_stream_request = (arg++)->AsInt32();
|
||||
info.port_osc = (arg++)->AsInt32();
|
||||
|
||||
// do we know this connection ?
|
||||
int i = Connection::manager().index(info);
|
||||
if ( i < 0) {
|
||||
// a new connection! Add to list
|
||||
Connection::manager().connections_.push_back(info);
|
||||
// replace instance name in settings
|
||||
// int id = info.port_handshake - HANDSHAKE_PORT;
|
||||
// Settings::application.instance_names[id] = info.name;
|
||||
|
||||
#ifdef CONNECTION_DEBUG
|
||||
Log::Info("List of connection updated:");
|
||||
Connection::manager().print();
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
// we know this connection: keep its status to ALIVE
|
||||
Connection::manager().connections_[i].alive = ALIVE;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
catch( osc::Exception& e ){
|
||||
// any parsing errors such as unexpected argument types, or
|
||||
// missing arguments get thrown as exceptions.
|
||||
Log::Info("error while parsing message '%s' from %s : %s", m.AddressPattern(), sender, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
97
Connection.h
Normal file
97
Connection.h
Normal file
@@ -0,0 +1,97 @@
|
||||
#ifndef CONNECTION_H
|
||||
#define CONNECTION_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "osc/OscReceivedElements.h"
|
||||
#include "osc/OscPacketListener.h"
|
||||
#include "ip/UdpSocket.h"
|
||||
|
||||
#include "NetworkToolkit.h"
|
||||
|
||||
#define ALIVE 3
|
||||
|
||||
class ConnectionRequestListener : public osc::OscPacketListener {
|
||||
|
||||
protected:
|
||||
virtual void ProcessMessage( const osc::ReceivedMessage& m,
|
||||
const IpEndpointName& remoteEndpoint );
|
||||
};
|
||||
|
||||
struct ConnectionInfo {
|
||||
|
||||
std::string address;
|
||||
int port_handshake;
|
||||
int port_stream_request;
|
||||
int port_osc;
|
||||
std::string name;
|
||||
int alive;
|
||||
|
||||
ConnectionInfo () {
|
||||
address = "127.0.0.1";
|
||||
port_handshake = HANDSHAKE_PORT;
|
||||
port_stream_request = STREAM_REQUEST_PORT;
|
||||
port_osc = OSC_DIALOG_PORT;
|
||||
name = "";
|
||||
alive = ALIVE;
|
||||
}
|
||||
|
||||
inline ConnectionInfo& operator = (const ConnectionInfo& o)
|
||||
{
|
||||
if (this != &o) {
|
||||
this->address = o.address;
|
||||
this->port_handshake = o.port_handshake;
|
||||
this->port_stream_request = o.port_stream_request;
|
||||
this->port_osc = o.port_osc;
|
||||
this->name = o.name;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline bool operator == (const ConnectionInfo& o) const
|
||||
{
|
||||
return this->address.compare(o.address) == 0
|
||||
&& this->port_handshake == o.port_handshake;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class Connection
|
||||
{
|
||||
friend class ConnectionRequestListener;
|
||||
|
||||
// Private Constructor
|
||||
Connection();
|
||||
Connection(Connection const& copy); // Not Implemented
|
||||
Connection& operator=(Connection const& copy); // Not Implemented
|
||||
|
||||
public:
|
||||
static Connection& manager()
|
||||
{
|
||||
// The only instance
|
||||
static Connection _instance;
|
||||
return _instance;
|
||||
}
|
||||
~Connection();
|
||||
|
||||
bool init();
|
||||
void terminate();
|
||||
|
||||
int numHosts () const;
|
||||
int index(ConnectionInfo i) const;
|
||||
int index(const std::string &name) const;
|
||||
ConnectionInfo info(int index = 0); // index 0 for self
|
||||
|
||||
private:
|
||||
|
||||
static void ask();
|
||||
static void listen();
|
||||
ConnectionRequestListener listener_;
|
||||
UdpListeningReceiveSocket *receiver_;
|
||||
|
||||
std::vector< ConnectionInfo > connections_;
|
||||
|
||||
void print();
|
||||
};
|
||||
|
||||
#endif // CONNECTION_H
|
||||
@@ -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");
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
60
FrameGrabber.h
Normal 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
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
35
Mixer.cpp
35
Mixer.cpp
@@ -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());
|
||||
}
|
||||
|
||||
1
Mixer.h
1
Mixer.h
@@ -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
315
NetworkSource.cpp
Normal 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
82
NetworkSource.h
Normal file
@@ -0,0 +1,82 @@
|
||||
#ifndef NETWORKSOURCE_H
|
||||
#define NETWORKSOURCE_H
|
||||
|
||||
#include "osc/OscReceivedElements.h"
|
||||
#include "osc/OscPacketListener.h"
|
||||
#include "osc/OscOutboundPacketStream.h"
|
||||
#include "ip/UdpSocket.h"
|
||||
|
||||
#include "NetworkToolkit.h"
|
||||
#include "Connection.h"
|
||||
#include "StreamSource.h"
|
||||
|
||||
class NetworkStream;
|
||||
|
||||
class StreamerResponseListener : public osc::OscPacketListener
|
||||
{
|
||||
protected:
|
||||
class NetworkStream *parent_;
|
||||
virtual void ProcessMessage( const osc::ReceivedMessage& m,
|
||||
const IpEndpointName& remoteEndpoint );
|
||||
public:
|
||||
inline void setParent(NetworkStream *s) { parent_ = s; }
|
||||
};
|
||||
|
||||
|
||||
class NetworkStream : public Stream
|
||||
{
|
||||
friend class StreamerResponseListener;
|
||||
|
||||
public:
|
||||
|
||||
NetworkStream();
|
||||
|
||||
void connect(const std::string &nameconnection);
|
||||
bool connected() const;
|
||||
void disconnect();
|
||||
|
||||
void update() override;
|
||||
|
||||
glm::ivec2 resolution() const;
|
||||
inline NetworkToolkit::Protocol protocol() const { return config_.protocol; }
|
||||
std::string clientAddress() const;
|
||||
std::string serverAddress() const;
|
||||
|
||||
private:
|
||||
// connection information
|
||||
ConnectionInfo streamer_;
|
||||
StreamerResponseListener listener_;
|
||||
UdpListeningReceiveSocket *receiver_;
|
||||
std::atomic<bool> received_config_;
|
||||
std::atomic<bool> connected_;
|
||||
|
||||
NetworkToolkit::StreamConfig config_;
|
||||
};
|
||||
|
||||
|
||||
class NetworkSource : public StreamSource
|
||||
{
|
||||
std::string connection_name_;
|
||||
|
||||
public:
|
||||
NetworkSource();
|
||||
~NetworkSource();
|
||||
|
||||
// Source interface
|
||||
void accept (Visitor& v) override;
|
||||
|
||||
// StreamSource interface
|
||||
Stream *stream() const override { return stream_; }
|
||||
NetworkStream *networkStream() const;
|
||||
|
||||
// specific interface
|
||||
void setConnection(const std::string &nameconnection);
|
||||
std::string connection() const;
|
||||
|
||||
glm::ivec2 icon() const override { return glm::ivec2(18, 11); }
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif // NETWORKSOURCE_H
|
||||
208
NetworkToolkit.cpp
Normal file
208
NetworkToolkit.cpp
Normal file
@@ -0,0 +1,208 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <net/if.h>
|
||||
#include <netdb.h>
|
||||
|
||||
#ifdef linux
|
||||
#include <linux/netdevice.h>
|
||||
#endif
|
||||
|
||||
// OSC IP gethostbyname
|
||||
#include "ip/NetworkingUtils.h"
|
||||
|
||||
#include "NetworkToolkit.h"
|
||||
|
||||
|
||||
/***
|
||||
*
|
||||
* TCP Server JPEG : broadcast
|
||||
* SND:
|
||||
* gst-launch-1.0 videotestsrc is-live=true ! jpegenc ! rtpjpegpay ! rtpstreampay ! tcpserversink port=5400
|
||||
* RCV:
|
||||
* gst-launch-1.0 tcpclientsrc port=5400 ! application/x-rtp-stream,encoding-name=JPEG ! rtpstreamdepay! rtpjpegdepay ! jpegdec ! autovideosink
|
||||
*
|
||||
* TCP Server H264 : broadcast
|
||||
* SND:
|
||||
* gst-launch-1.0 videotestsrc is-live=true ! x264enc ! rtph264pay ! rtpstreampay ! tcpserversink port=5400
|
||||
* RCV:
|
||||
* gst-launch-1.0 tcpclientsrc port=5400 ! application/x-rtp-stream,media=video,encoding-name=H264,payload=96,clock-rate=90000 ! rtpstreamdepay ! rtpjitterbuffer ! rtph264depay ! avdec_h264 ! autovideosink
|
||||
*
|
||||
* UDP unicast
|
||||
* SND
|
||||
* gst-launch-1.0 videotestsrc is-live=true ! videoconvert ! video/x-raw, format=RGB, framerate=30/1 ! queue max-size-buffers=3 ! jpegenc ! rtpjpegpay ! udpsink port=5000 host=127.0.0.1
|
||||
* RCV
|
||||
* gst-launch-1.0 udpsrc port=5000 ! application/x-rtp,encoding-name=JPEG,payload=26 ! rtpjpegdepay ! jpegdec ! autovideosink
|
||||
*
|
||||
* * UDP multicast : hass to know the PORT and IP of all clients
|
||||
* SND
|
||||
* gst-launch-1.0 videotestsrc is-live=true ! jpegenc ! rtpjpegpay ! multiudpsink clients="127.0.0.1:5000,127.0.0.1:5001"
|
||||
* RCV
|
||||
* gst-launch-1.0 -v udpsrc port=5000 ! application/x-rtp,encoding-name=JPEG,payload=26 ! rtpjpegdepay ! jpegdec ! autovideosink
|
||||
* gst-launch-1.0 -v udpsrc port=5001 ! application/x-rtp,encoding-name=JPEG,payload=26 ! rtpjpegdepay ! jpegdec ! autovideosink
|
||||
*
|
||||
* RAW UDP (caps has to match exactly, and depends on resolution)
|
||||
* SND
|
||||
* gst-launch-1.0 -v videotestsrc is-live=true ! video/x-raw,format=RGBA,width=1920,height=1080 ! rtpvrawpay ! udpsink port=5000 host=127.0.0.1
|
||||
* RCV
|
||||
* gst-launch-1.0 udpsrc port=5000 caps = "application/x-rtp, media=(string)video, clock-rate=(int)90000, encoding-name=(string)RAW, sampling=(string)RGBA, depth=(string)8, width=(string)1920, height=(string)1080, colorimetry=(string)SMPTE240M, payload=(int)96, ssrc=(uint)2272750581, timestamp-offset=(uint)1699493959, seqnum-offset=(uint)14107, a-framerate=(string)30" ! rtpvrawdepay ! videoconvert ! autovideosink
|
||||
*
|
||||
*
|
||||
* SHM RAW RGB
|
||||
* SND
|
||||
* gst-launch-1.0 videotestsrc is-live=true ! video/x-raw, format=RGB, framerate=30/1 ! shmsink socket-path=/tmp/blah
|
||||
* RCV
|
||||
* gst-launch-1.0 shmsrc is-live=true socket-path=/tmp/blah ! video/x-raw, format=RGB, framerate=30/1, width=320, height=240 ! videoconvert ! autovideosink
|
||||
*
|
||||
* */
|
||||
|
||||
const char* NetworkToolkit::protocol_name[NetworkToolkit::DEFAULT] = {
|
||||
"Shared Memory",
|
||||
"RTP JPEG Stream",
|
||||
"RTP H264 Stream",
|
||||
"RTP JPEG Broadcast",
|
||||
"RTP H264 Broadcast"
|
||||
};
|
||||
|
||||
const std::vector<std::string> NetworkToolkit::protocol_send_pipeline {
|
||||
|
||||
"video/x-raw, format=RGB, framerate=30/1 ! queue max-size-buffers=10 ! shmsink buffer-time=100000 wait-for-connection=true name=sink",
|
||||
"video/x-raw, format=I420, framerate=30/1 ! queue max-size-buffers=10 ! jpegenc ! rtpjpegpay ! udpsink name=sink",
|
||||
"video/x-raw, format=I420, framerate=30/1 ! queue max-size-buffers=10 ! x264enc tune=\"zerolatency\" threads=2 ! rtph264pay ! udpsink name=sink",
|
||||
"video/x-raw, format=I420, framerate=30/1 ! queue max-size-buffers=3 ! jpegenc ! rtpjpegpay ! rtpstreampay ! tcpserversink name=sink",
|
||||
"video/x-raw, format=I420, framerate=30/1 ! queue max-size-buffers=3 ! x264enc tune=\"zerolatency\" threads=2 ! rtph264pay ! rtpstreampay ! tcpserversink name=sink"
|
||||
};
|
||||
|
||||
const std::vector<std::string> NetworkToolkit::protocol_receive_pipeline {
|
||||
|
||||
"shmsrc socket-path=XXXX ! video/x-raw, format=RGB, framerate=30/1 ! queue max-size-buffers=10",
|
||||
"udpsrc buffer-size=200000 port=XXXX ! application/x-rtp,encoding-name=JPEG,payload=26,clock-rate=90000 ! queue max-size-buffers=10 ! rtpjpegdepay ! jpegdec",
|
||||
"udpsrc buffer-size=200000 port=XXXX ! application/x-rtp,encoding-name=H264,payload=96,clock-rate=90000 ! queue ! rtph264depay ! avdec_h264",
|
||||
"tcpclientsrc timeout=1 port=XXXX ! queue max-size-buffers=30 ! application/x-rtp-stream,media=video,encoding-name=JPEG,payload=26,clock-rate=90000 ! rtpstreamdepay ! rtpjpegdepay ! jpegdec",
|
||||
"tcpclientsrc timeout=1 port=XXXX ! queue max-size-buffers=30 ! application/x-rtp-stream,media=video,encoding-name=H264,payload=96,clock-rate=90000 ! rtpstreamdepay ! rtph264depay ! avdec_h264"
|
||||
};
|
||||
|
||||
bool initialized_ = false;
|
||||
std::vector<std::string> ipstrings_;
|
||||
std::vector<unsigned long> iplongs_;
|
||||
|
||||
|
||||
void add_interface(int fd, const char *name) {
|
||||
struct ifreq ifreq;
|
||||
char host[128];
|
||||
memset(&ifreq, 0, sizeof ifreq);
|
||||
strncpy(ifreq.ifr_name, name, IFNAMSIZ);
|
||||
if(ioctl(fd, SIOCGIFADDR, &ifreq)==0) {
|
||||
int family;
|
||||
switch(family=ifreq.ifr_addr.sa_family) {
|
||||
case AF_INET:
|
||||
case AF_INET6:
|
||||
getnameinfo(&ifreq.ifr_addr, sizeof ifreq.ifr_addr, host, sizeof host, 0, 0, NI_NUMERICHOST);
|
||||
break;
|
||||
default:
|
||||
case AF_UNSPEC:
|
||||
return; /* ignore */
|
||||
}
|
||||
// add only if not already listed
|
||||
std::string hostip(host);
|
||||
if ( std::find(ipstrings_.begin(), ipstrings_.end(), hostip) == ipstrings_.end() )
|
||||
{
|
||||
ipstrings_.push_back( hostip );
|
||||
iplongs_.push_back( GetHostByName(host) );
|
||||
// printf("%s %s %lu\n", name, host, GetHostByName(host));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void list_interfaces()
|
||||
{
|
||||
struct ifreq *ifreq;
|
||||
struct ifconf ifconf;
|
||||
char buf[16384];
|
||||
int fd=socket(PF_INET, SOCK_DGRAM, 0);
|
||||
if(fd > -1) {
|
||||
ifconf.ifc_len=sizeof buf;
|
||||
ifconf.ifc_buf=buf;
|
||||
if(ioctl(fd, SIOCGIFCONF, &ifconf)==0) {
|
||||
ifreq=ifconf.ifc_req;
|
||||
for(int i=0;i<ifconf.ifc_len;) {
|
||||
size_t len;
|
||||
#ifndef linux
|
||||
len=IFNAMSIZ + ifreq->ifr_addr.sa_len;
|
||||
#else
|
||||
len=sizeof *ifreq;
|
||||
#endif
|
||||
add_interface(fd, ifreq->ifr_name);
|
||||
ifreq=(struct ifreq*)((char*)ifreq+len);
|
||||
i+=len;
|
||||
}
|
||||
}
|
||||
}
|
||||
close(fd);
|
||||
initialized_ = true;
|
||||
}
|
||||
|
||||
std::vector<std::string> NetworkToolkit::host_ips()
|
||||
{
|
||||
if (!initialized_)
|
||||
list_interfaces();
|
||||
|
||||
return ipstrings_;
|
||||
}
|
||||
|
||||
|
||||
bool NetworkToolkit::is_host_ip(const std::string &ip)
|
||||
{
|
||||
if ( ip.compare("localhost") == 0)
|
||||
return true;
|
||||
|
||||
if (!initialized_)
|
||||
list_interfaces();
|
||||
|
||||
return std::find(ipstrings_.begin(), ipstrings_.end(), ip) != ipstrings_.end();
|
||||
}
|
||||
|
||||
std::string NetworkToolkit::closest_host_ip(const std::string &ip)
|
||||
{
|
||||
std::string address = "localhost";
|
||||
|
||||
if (!initialized_)
|
||||
list_interfaces();
|
||||
|
||||
// discard trivial case
|
||||
if ( ip.compare("localhost") != 0)
|
||||
{
|
||||
int index_mini = -1;
|
||||
unsigned long host = GetHostByName( ip.c_str() );
|
||||
unsigned long mini = host;
|
||||
|
||||
for (size_t i=0; i < iplongs_.size(); i++){
|
||||
unsigned long diff = host > iplongs_[i] ? host-iplongs_[i] : iplongs_[i]-host;
|
||||
if (diff < mini) {
|
||||
mini = diff;
|
||||
index_mini = (int) i;
|
||||
}
|
||||
}
|
||||
|
||||
if (index_mini>0)
|
||||
address = ipstrings_[index_mini];
|
||||
|
||||
}
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
std::string NetworkToolkit::hostname()
|
||||
{
|
||||
char hostname[1024];
|
||||
hostname[1023] = '\0';
|
||||
gethostname(hostname, 1023);
|
||||
|
||||
return std::string(hostname);
|
||||
}
|
||||
78
NetworkToolkit.h
Normal file
78
NetworkToolkit.h
Normal file
@@ -0,0 +1,78 @@
|
||||
#ifndef NETWORKTOOLKIT_H
|
||||
#define NETWORKTOOLKIT_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#define OSC_PREFIX "/vimix"
|
||||
#define OSC_PING "/ping"
|
||||
#define OSC_PONG "/pong"
|
||||
#define OSC_STREAM_REQUEST "/request"
|
||||
#define OSC_STREAM_OFFER "/offer"
|
||||
#define OSC_STREAM_REJECT "/reject"
|
||||
#define OSC_STREAM_DISCONNECT "/disconnect"
|
||||
|
||||
|
||||
#define MAX_HANDSHAKE 20
|
||||
#define HANDSHAKE_PORT 71310
|
||||
#define STREAM_REQUEST_PORT 71510
|
||||
#define OSC_DIALOG_PORT 71010
|
||||
#define IP_MTU_SIZE 1536
|
||||
|
||||
namespace NetworkToolkit
|
||||
{
|
||||
|
||||
typedef enum {
|
||||
SHM_RAW = 0,
|
||||
UDP_JPEG,
|
||||
UDP_H264,
|
||||
TCP_JPEG,
|
||||
TCP_H264,
|
||||
DEFAULT
|
||||
} Protocol;
|
||||
|
||||
|
||||
struct StreamConfig {
|
||||
|
||||
Protocol protocol;
|
||||
std::string client_name;
|
||||
std::string client_address;
|
||||
int port;
|
||||
int width;
|
||||
int height;
|
||||
|
||||
StreamConfig () {
|
||||
protocol = DEFAULT;
|
||||
client_name = "";
|
||||
client_address = "127.0.0.1";
|
||||
port = 0;
|
||||
width = 0;
|
||||
height = 0;
|
||||
}
|
||||
|
||||
inline StreamConfig& operator = (const StreamConfig& o)
|
||||
{
|
||||
if (this != &o) {
|
||||
this->client_name = o.client_name;
|
||||
this->client_address = o.client_address;
|
||||
this->port = o.port;
|
||||
this->protocol = o.protocol;
|
||||
this->width = o.width;
|
||||
this->height = o.height;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
extern const char* protocol_name[DEFAULT];
|
||||
extern const std::vector<std::string> protocol_send_pipeline;
|
||||
extern const std::vector<std::string> protocol_receive_pipeline;
|
||||
|
||||
std::string hostname();
|
||||
std::vector<std::string> host_ips();
|
||||
bool is_host_ip(const std::string &ip);
|
||||
std::string closest_host_ip(const std::string &ip);
|
||||
|
||||
}
|
||||
|
||||
#endif // NETWORKTOOLKIT_H
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
52
Recorder.cpp
52
Recorder.cpp
@@ -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)
|
||||
{
|
||||
|
||||
40
Recorder.h
40
Recorder.h
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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_; }
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
61
Session.cpp
61
Session.cpp
@@ -5,7 +5,7 @@
|
||||
#include "FrameBuffer.h"
|
||||
#include "Session.h"
|
||||
#include "GarbageVisitor.h"
|
||||
#include "Recorder.h"
|
||||
#include "FrameGrabber.h"
|
||||
#include "SessionCreator.h"
|
||||
|
||||
#include "Log.h"
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
15
Session.h
15
Session.h
@@ -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_;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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() );
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
75
Settings.cpp
75
Settings.cpp
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
15
Settings.h
15
Settings.h
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
39
Stream.cpp
39
Stream.cpp
@@ -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
596
Streamer.cpp
Normal 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
110
Streamer.h
Normal 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
|
||||
@@ -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();;
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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&) {}
|
||||
|
||||
@@ -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
34
ext/OSCPack/LICENSE
Normal 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
150
ext/OSCPack/README
Normal 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.
|
||||
|
||||
###
|
||||
88
ext/OSCPack/ip/IpEndpointName.cpp
Normal file
88
ext/OSCPack/ip/IpEndpointName.cpp
Normal 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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
83
ext/OSCPack/ip/IpEndpointName.h
Normal file
83
ext/OSCPack/ip/IpEndpointName.h
Normal 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 */
|
||||
56
ext/OSCPack/ip/NetworkingUtils.h
Normal file
56
ext/OSCPack/ip/NetworkingUtils.h
Normal 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 */
|
||||
50
ext/OSCPack/ip/PacketListener.h
Normal file
50
ext/OSCPack/ip/PacketListener.h
Normal 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 */
|
||||
47
ext/OSCPack/ip/TimerListener.h
Normal file
47
ext/OSCPack/ip/TimerListener.h
Normal 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
176
ext/OSCPack/ip/UdpSocket.h
Normal 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 */
|
||||
64
ext/OSCPack/ip/posix/NetworkingUtils.cpp
Normal file
64
ext/OSCPack/ip/posix/NetworkingUtils.cpp
Normal 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;
|
||||
}
|
||||
602
ext/OSCPack/ip/posix/UdpSocket.cpp
Normal file
602
ext/OSCPack/ip/posix/UdpSocket.cpp
Normal 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();
|
||||
}
|
||||
|
||||
95
ext/OSCPack/ip/win32/NetworkingUtils.cpp
Normal file
95
ext/OSCPack/ip/win32/NetworkingUtils.cpp
Normal 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;
|
||||
}
|
||||
571
ext/OSCPack/ip/win32/UdpSocket.cpp
Normal file
571
ext/OSCPack/ip/win32/UdpSocket.cpp
Normal 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();
|
||||
}
|
||||
|
||||
80
ext/OSCPack/osc/MessageMappingOscPacketListener.h
Normal file
80
ext/OSCPack/osc/MessageMappingOscPacketListener.h
Normal 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 */
|
||||
62
ext/OSCPack/osc/OscException.h
Normal file
62
ext/OSCPack/osc/OscException.h
Normal 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 */
|
||||
63
ext/OSCPack/osc/OscHostEndianness.h
Normal file
63
ext/OSCPack/osc/OscHostEndianness.h
Normal 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
|
||||
683
ext/OSCPack/osc/OscOutboundPacketStream.cpp
Normal file
683
ext/OSCPack/osc/OscOutboundPacketStream.cpp
Normal 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
|
||||
|
||||
|
||||
154
ext/OSCPack/osc/OscOutboundPacketStream.h
Normal file
154
ext/OSCPack/osc/OscOutboundPacketStream.h
Normal 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 */
|
||||
79
ext/OSCPack/osc/OscPacketListener.h
Normal file
79
ext/OSCPack/osc/OscPacketListener.h
Normal 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 */
|
||||
261
ext/OSCPack/osc/OscPrintReceivedElements.cpp
Normal file
261
ext/OSCPack/osc/OscPrintReceivedElements.cpp
Normal 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
|
||||
54
ext/OSCPack/osc/OscPrintReceivedElements.h
Normal file
54
ext/OSCPack/osc/OscPrintReceivedElements.h
Normal 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 */
|
||||
796
ext/OSCPack/osc/OscReceivedElements.cpp
Normal file
796
ext/OSCPack/osc/OscReceivedElements.cpp
Normal 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
|
||||
|
||||
548
ext/OSCPack/osc/OscReceivedElements.h
Normal file
548
ext/OSCPack/osc/OscReceivedElements.h
Normal 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 */
|
||||
52
ext/OSCPack/osc/OscTypes.cpp
Normal file
52
ext/OSCPack/osc/OscTypes.cpp
Normal 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
240
ext/OSCPack/osc/OscTypes.h
Normal 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 */
|
||||
43
main.cpp
43
main.cpp
@@ -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
|
||||
///
|
||||
|
||||
@@ -11,4 +11,4 @@ void * get_current_nsopengl_context();
|
||||
|
||||
};
|
||||
|
||||
#endif // COCOA_TOOLKIT_H
|
||||
#endif // COCOA_TOOLKIT_H
|
||||
|
||||
10
osx/CustomDelegate.h
Normal file
10
osx/CustomDelegate.h
Normal 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
49
osx/CustomDelegate.m
Normal 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
|
||||
@@ -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
442
rsc/mesh/icon_share.ply
Normal 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
|
||||
Reference in New Issue
Block a user