![]() |
O2
1.1
Inter-process communication system for media applications
|
Data Structures | |
struct | o2_msg_data |
data part of an O2 message More... | |
struct | o2_message |
an O2 message container More... | |
struct | o2_blob |
The structure for binary large object. More... | |
union | o2_arg |
union of all O2 parameter types More... | |
Macros | |
#define | O2_MALLOC(x) o2_dbg_malloc(x, __FILE__, __LINE__) |
#define | O2_FREE(x) o2_dbg_free(x, __FILE__, __LINE__) |
#define | O2_CALLOC(n, s) o2_dbg_calloc((n), (s), __FILE__, __LINE__) |
#define | MSG_DATA_LENGTH(m) (((int32_t *) &((m)->timestamp))[-1]) |
#define | WORD_ALIGN_PTR(p) ((char *) (((size_t) (p)) & ~3)) |
get the type string from o2_msg_data_ptr More... | |
#define | O2_MSG_TYPES(msg) WORD_ALIGN_PTR((msg)->address + strlen((msg)->address) + 4) + 1; |
#define | o2_send(path, time, ...) |
Construct and send O2 message with best effort protocol. More... | |
#define | o2_send_cmd(path, time, ...) |
Construct and send an O2 message reliably. More... | |
Typedefs | |
typedef double | o2_time |
O2 timestamps are doubles representing seconds since the approximate start time of the application. | |
typedef struct o2_msg_data | o2_msg_data |
data part of an O2 message More... | |
typedef struct o2_msg_data * | o2_msg_data_ptr |
typedef struct o2_message | o2_message |
an O2 message container More... | |
typedef struct o2_message * | o2_message_ptr |
typedef struct o2_blob | o2_blob |
The structure for binary large object. More... | |
typedef struct o2_blob * | o2_blob_ptr |
typedef enum o2_type * | o2_type_ptr |
typedef union o2_arg * | o2_arg_ptr |
typedef void(* | o2_method_handler) (const o2_msg_data_ptr msg, const char *types, o2_arg_ptr *argv, int argc, void *user_data) |
callback function to receive an O2 message More... | |
typedef o2_time(* | o2_time_callback) (void *rock) |
signature for callback that defines the master clock More... | |
Enumerations | |
enum | o2_type { O2_INT32 = 'i', O2_FLOAT = 'f', O2_STRING = 's', O2_BLOB = 'b', O2_ARRAY_START = '[', O2_ARRAY_END = ']', O2_INT64 = 'h', O2_TIME = 't', O2_DOUBLE = 'd', O2_SYMBOL = 'S', O2_CHAR = 'c', O2_MIDI = 'm', O2_TRUE = 'T', O2_FALSE = 'F', O2_NIL = 'N', O2_INFINITUM = 'I', O2_BOOL = 'B', O2_VECTOR = 'v' } |
An enumeration of the O2 message types. More... | |
Functions | |
void * | o2_dbg_malloc (size_t size, char *file, int line) |
void | o2_dbg_free (void *obj, char *file, int line) |
void * | o2_dbg_calloc (size_t n, size_t s, char *file, int line) |
allocate memory More... | |
int | o2_initialize (const char *application_name) |
Start O2. More... | |
int | o2_memory (void *((*malloc)(size_t size)), void((*free)(void *))) |
Tell O2 how to allocate/free memory. More... | |
o2_time | o2_set_discovery_period (o2_time period) |
Set discovery period. More... | |
int | o2_service_new (const char *service_name) |
Add a service to the current application. More... | |
int | o2_service_free (char *service_name) |
Remove a local service. More... | |
int | o2_method_new (const char *path, const char *typespec, o2_method_handler h, void *user_data, int coerce, int parse) |
Add a handler for an address. More... | |
int | o2_poll () |
Process current O2 messages. More... | |
int | o2_run (int rate) |
Run O2. More... | |
int | o2_status (const char *service) |
Check the status of the service. More... | |
int | o2_roundtrip (double *mean, double *min) |
Get network round-trip information. More... | |
int | o2_clock_set (o2_time_callback gettime, void *rock) |
Provide a time reference to O2. More... | |
int | o2_message_send (o2_message_ptr msg) |
Send an O2 message. (See also macros o2_send and o2_send_cmd). More... | |
o2_time | o2_time_get () |
Get the estimated synchronized global O2 time. More... | |
o2_time | o2_local_time () |
Get the real time using the local O2 clock. More... | |
const char * | o2_error_to_string (int i) |
Return text representation of an O2 error. More... | |
int | o2_finish () |
release the memory and shut down O2. More... | |
int | o2_osc_port_new (const char *service_name, int port_num, int tcp_flag) |
Create a port to receive OSC messages. More... | |
int | o2_osc_port_free (int port_num) |
Remove a port receiving OSC messages. More... | |
int | o2_osc_delegate (const char *service_name, const char *ip, int port_num, int tcp_flag) |
Create a service that forwards O2 messages to an OSC server. More... | |
uint64_t | o2_osc_time_offset (uint64_t offset) |
Set the OSC time offset. More... | |
int | o2_schedule (o2_sched_ptr scheduler, o2_message_ptr msg) |
Variables | |
o2_arg_ptr | o2_got_start_array |
o2_arg_ptr | o2_got_end_array |
const char * | o2_application_name |
name of the application More... | |
int | o2_stop_flag |
set this flag to stop o2_run() More... | |
int | o2_clock_is_synchronized |
A variable indicating that the clock is the master or is synchronized to the master. | |
o2_sched | o2_gtsched |
Scheduler that schedules according to global (master) clock time. More... | |
o2_sched | o2_ltsched |
Scheduler that schedules according to local clock time. More... | |
o2_sched_ptr | o2_active_sched |
Current scheduler. More... | |
#define o2_send | ( | path, | |
time, | |||
... | |||
) |
Construct and send O2 message with best effort protocol.
Normally, this constructs and sends an O2 message via UDP. If the destination service is reached via some other network protocol (e.g. Bluetooth), the message is delivered in the lowest latency protocol available, with no guaranteed delivery.
path | an address pattern |
time | when to dispatch the message, 0 means right now. In any case, the message is sent to the receiving service as soon as possible. If the message arrives early, it will be held at the service and dispatched as soon as possible after the indicated time. |
typestring | the type string for the message. Each character indicates one data item. Type codes are as in OSC. |
... | the data of the message. There is one parameter for each character in the typestring. |
#define o2_send_cmd | ( | path, | |
time, | |||
... | |||
) |
Construct and send an O2 message reliably.
Normally, this constructs and sends an O2 message via TCP. If the destination service is reached via some other network protocol (e.g. Bluetooth), the message is delivered using the most reliable protocol available. (Thus, this call is considered a "hint" rather than an absolute requirement.)
path | an address pattern |
time | when to dispatch the message, 0 means right now. In any case, the message is sent to the receiving service as soon as possible. If the message arrives early, it will be held at the service and dispatched as soon as possible after the indicated time. |
typestring | the type string for the message. Each character indicates one data item. Type codes are defined by o2_type. |
... | the data of the message. There is one parameter for each character in the typestring. |
#define WORD_ALIGN_PTR | ( | p | ) | ((char *) (((size_t) (p)) & ~3)) |
get the type string from o2_msg_data_ptr
Type strings begin with the comma (",") character, which is skipped
The structure for binary large object.
A blob can be passed in an O2 message using the 'b' type. Created by calls to o2_blob_new().
typedef struct o2_message o2_message |
an O2 message container
Note: This struct represents an O2 message that is stored on the heap. The length field must preceded data with no padding (see o2_msg_data declaration and the note that precedes it). To make sure there is no padding between length and data, we force the next pointer to occupy 8 bytes even if this is a 32-bit machine by making it part of a union with an 8-byte int64_t field named "pad_if_needed."
Note that o2_messages are on the heap and can be allocated, scheduled, sent, and freed. In contrast, o2_msg_data structures are contained within o2_messages and are passed to method handlers, but cannot be allocated, scheduled, sent, or freed. They are always the data field of a containing o2_message.
typedef void(* o2_method_handler) (const o2_msg_data_ptr msg, const char *types, o2_arg_ptr *argv, int argc, void *user_data) |
callback function to receive an O2 message
msg | The full message in host byte order. |
types | If you set a type string in your method creation call, then this type string is provided here. If you did not specify a string, types will be the type string from the message (without the initial ','). If parse_args and coerce_flag were set in the method creation call, types will match the types in argv, but not necessarily the type string or types in msg. |
argv | An array of o2_arg types containing the values, e.g. if the first argument of the incoming message is of type 'f' then the value will be found in argv[0]->f. (If parse_args was not set in the method creation call, argv will be NULL.) For vectors, specified in types by the sequence "vi", "vh", "vf", or "vd", there will be one pointer in argv pointing to a vector description (the v field in o2_arg). For arrays, there are no pointers corresponding to '[' or ']' in the types string; but there is one pointer in argv for each array element. |
argc | The number of arguments received. (This is valid even if parse_args was not set in the method creation call.) This is the length of argv. Vectors count as one, array elements count as one each, and arrays themselves are not represented. For example, an empty array ("[]") in the type string adds nothing to the argc count or argv vector. |
user_data | This contains the user_data value passed in the call to the method creation call. |
typedef struct o2_msg_data o2_msg_data |
data part of an O2 message
This data type is used to pass o2 message data to message handlers. It appears many other times in the code. You should NEVER allocate or free an o2_msg_data struct. Instead, create a message using o2_send_start(), o2_add_*(), and o2_message_finish() to get an o2_message_ptr. Within the o2_message, the data field is an o2_msg_data structure. We would use o2_message everywhere instead of o2_msg_data, but bundles can contain multiple o2_msg_data structures without the extra baggage contained in an o2_message.
Note: it is assumed that an o2_msg_data struct is always preceded by a 32-bit length. Ideally, length should therefore be in this struct, but then the compiler might add padding to put the timestamp on an 8-byte alignment. This could be solved with a pack pragma, but that is not standard C. To be safe and portable, I decided to just leave length out of the struct. The macro MSG_DATA_LENGTH can be used to access the length field.
typedef o2_time(* o2_time_callback) (void *rock) |
signature for callback that defines the master clock
See o2_clock_set() for details.
enum o2_type |
An enumeration of the O2 message types.
int o2_clock_set | ( | o2_time_callback | gettime, |
void * | rock | ||
) |
Provide a time reference to O2.
Exactly one process per O2 application should provide a master clock. All other processes synchronize to the master. To become the master, call o2_clock_set().
The time reported by the gettime function will be offset to match the current local time so that local time continues to increase smoothly. You cannot force O2 time to match an external absolute time, but once o2_clock_set() is called, the difference between the time reference and O2's local time (as reported by o2_local_time()) will be fixed.
gettime | function to get the time in units of seconds. The reference may be operating system time, audio system time, MIDI system time, or any other time source. The times returned by this function must be non-decreasing and must increase by one second per second of real time to close approximation. The value may be NULL, in which case a default time reference will be used. |
@parm rock an arbitrary value that is passed to the gettime function. This may be need to provide context. Use NULL if no context is required.
void* o2_dbg_calloc | ( | size_t | n, |
size_t | s, | ||
char * | file, | ||
int | line | ||
) |
allocate memory
O2 allows you to provide custom heap implementations to avoid priority inversion or other real-time problems. Normally, you should not need to explicitly allocate memory since O2 functions are provided to allocate, construct, and deallocate messages, but if you need to allocate memory, especially in an O2 message handler callback, i.e. within the sphere of O2 execution, you should use #O2_MALLOC, #O2_FREE, and #O2_CALLOC.
free memory allocated by #O2_MALLOC
allocate and zero memory (see #O2_MALLOC)
const char* o2_error_to_string | ( | int | i | ) |
Return text representation of an O2 error.
i | error number returned from some O2 function |
int o2_finish | ( | ) |
release the memory and shut down O2.
Close all sockets, free all memory, and restore critical variables so that O2 behaves as if it was never initialized.
int o2_initialize | ( | const char * | application_name | ) |
Start O2.
If O2 has not been initialized, it is created and intialized. O2 will begin to establish connections to other instances with a matching application name.
application_name | the name of the application. O2 will attempt to discover other processes with a matching application name, ignoring all processes with non-matching names. |
application_name
is NULL. o2_time o2_local_time | ( | ) |
Get the real time using the local O2 clock.
int o2_memory | ( | void * | (*malloc)(size_t size), |
void((*free)(void *)) | |||
) |
Tell O2 how to allocate/free memory.
In many C library implementations, the standard implementation of free() must lock a data structure. This can lead to priority inversion if O2 runs at an elevated priority. Furthermore, the standard malloc()
and free()
do not run in constant (real) time. To avoid these problems, you can provide an alternate heap implementation for O2 by calling this function before calling o2_initialize(). For example, the provided functions can implement a private heap for the thread running O2.
malloc | a function pointer that behaves like standard malloc() |
free | a function pointer that behaves like standard free() |
int o2_message_send | ( | o2_message_ptr | msg | ) |
Send an O2 message. (See also macros o2_send and o2_send_cmd).
msg | points to an O2 message. |
After the call, the msg
parameter is "owned" by O2, which will free it. Therefore, do not free msg after calling o2_message_send().
int o2_method_new | ( | const char * | path, |
const char * | typespec, | ||
o2_method_handler | h, | ||
void * | user_data, | ||
int | coerce, | ||
int | parse | ||
) |
Add a handler for an address.
path | the address including the service name. If the address is only the service name with no trailing slash, the handler will match any message to the service. Addresses should not conflict: An address should not match another address, and for every pair of addresses X and Y, X/ should not be a prefix of Y. |
typespec | the types of parameters, use "" for no parameters and NULL for no type checking |
h | the handler |
user_data | pointer saved and passed to handler |
coerce | is true if you want to allow automatic coercion of types. Coercion is only enabled if both coerce and parse are true. |
parse | is true if you want O2 to construct an argv argument vector to pass to the handle |
int o2_osc_delegate | ( | const char * | service_name, |
const char * | ip, | ||
int | port_num, | ||
int | tcp_flag | ||
) |
Create a service that forwards O2 messages to an OSC server.
service_name | The o2 service name without a '/' prefix. |
ip | The ip address of the osc server. |
port_num | The port number of the osc server. |
tcp_flag | Send OSC message via TCP protocol, in which case port_num is the TCP server port, not a connection. |
If tcp_flag
is set, a TCP connection will be established with the OSC server. When the created service receives any O2 messages, it will send the message to the OSC server. If the incoming message has a timestamp for some future time, the message will be held until that time, then sent to the OSC server. (Ideally, O2 could convert the message to an OSC timestamped bundle and send it immediately to achieve precise forward-synchronous timing, but this requires clock synchronization with the OSC server, which is normally unimplemented.)
If this is a tcp connection, close it by calling o2_service_free().
int o2_osc_port_free | ( | int | port_num | ) |
Remove a port receiving OSC messages.
This removes a port created by o2_osc_port_new(). If you want to remove the corresponding service, you must also call o2_service_free() with the service name.
port_num | The port number that receives OSC messages. |
int o2_osc_port_new | ( | const char * | service_name, |
int | port_num, | ||
int | tcp_flag | ||
) |
Create a port to receive OSC messages.
OSC messages are converted to O2 messages and directed to the service. E.g. if the service is "maxmsp" and the message address is /foo/x
, then the message is directed to and handled by /maxmsp/foo/x
. If the #service_name does not exist at any time after calling o2_osc_port_new, incoming OSC messages will be dropped until the service is available again.
service_name | The name of the service to which messages are delivered |
port_num | Port number. |
tcp_flag | Be a TCP server for remote clients. Otherwise, use UDP |
uint64_t o2_osc_time_offset | ( | uint64_t | offset | ) |
Set the OSC time offset.
offset | the offset between (global) O2 time and OSC time |
O2 global time should start from 0.0 when the clock is started, whereas OSC time starts at 1 Jan 1900. The offset is the OSC time corresponding to O2 time 0.0. Equivalently, OSC_time = O2_time + offset.
int o2_poll | ( | ) |
Process current O2 messages.
Since O2 does not create a thread and O2 requires active processing to establish and maintain connections, the O2 programmer (user) should call o2_poll() periodically, even if not offering a service. o2_poll() runs a discovery protocol to find and connect to other processes, runs a clock synchronization protocol to establish valid time stamps, and handles incoming messages to all services. O2_poll() should be called at least 10 times per second. Messages can only be delivered during a call to o2_poll() so more frequent calls will generally lower the message latency as well as the accuracy of the clock synchronization (at the cost of greater CPU utilization). Human perception of timing jitter is on the order of 10ms, so polling rates of 200 to 1000 are advised in situations where rhythmic accuracy is expected.
int o2_roundtrip | ( | double * | mean, |
double * | min | ||
) |
Get network round-trip information.
*mean
to the mean round-trip time and *min
to the minimum round-trip time of the last 5 (where 5 is the value of CLOCK_SYNC_HISTORY_LEN) clock sync requests. Otherwise, O2_FAIL is returned and *mean
and *min
are unaltered. int o2_run | ( | int | rate | ) |
Run O2.
Call o2_poll() at the rate (in Hz) indicated. Returns if a handler sets o2_stop_flag to non-zero.
int o2_schedule | ( | o2_sched_ptr | scheduler, |
o2_message_ptr | msg | ||
) |
/brief Schedule a message.
Rather than sending a message, messages can be directly scheduled. This is particulary useful if you want to schedule activity before clock synchronization is achieved. For example, you might want to poll every second waiting for clock synchronization. In that case, you need to use the local scheduler (o2_ltsched). o2_send() will use the global time scheduler (o2_gtsched), so your only option is to construct a message and call o2_schedule().
scheduler | a pointer to a scheduler (&o2_ltsched or &o2_gtsched ) |
msg | a pointer to the message to schedule |
The message is scheduled for delivery according to its timestamp (which is interpreted as local or global time depending on the scheduler).
The message is delivered immediately if the time is zero or less than the current time; however, to avoid unbounded recursion, messages scheduled within handlers are appended to a "pending messages" queue and delivered after the handler returns.
int o2_service_free | ( | char * | service_name | ) |
Remove a local service.
The #service_name corresponds to the parameter previously passed to o2_service_new or o2_osc_delegate. Note that if an OSC port forwards to this service (see o2_osc_port_new), the port remains open, but the OSC messages will be dropped. See o2_osc_port_free().
service_name | the name of the service |
int o2_service_new | ( | const char * | service_name | ) |
Add a service to the current application.
Once created, services are "advertised" to other processes with matching application names, and messages are delivered accordingly. E.g. to handle messages addressed to "/synth/volume" you call
and define synth_volume_handler
(see the type declaration for o2_method_handler and o2_method_new()) Normally, services should be unique across the application. If #service_name is already locally defined in this process (by a previous call to o2_service_new or o2_osc_delegate), this call will fail, returning O2_SERVICE_EXISTS. If matching service names are defined in two different processes, the process with the highest IP and port number (lexicographically) will provide the service. However, due to the distributed and asynchronous nature of O2, there may be some intervening time (typically a fraction of a second) during which a service is handled by two different processes. Furthermore, the switch to a new service provider could redirect a stream of messages, causing unexpected behavior in the application.
service_name | the name of the service |
Set discovery period.
O2 discovery messages are broadcast periodically in case a new process has joined the application. The default period is 4 seconds. If there are N processes, each host will receive N/4 discovery messages per second. Since there are 5 discovery ports, each process will handle N/20 discovery messages per second, and a discovery message from any given process will be received every 20 seconds. (Note, however, that new processes send more frequently, sending 2 discovery messages to each of the 5 discovery port numbers within 2 seconds, so if messages are not dropped frequently, discovery of new processes will happen much faster than the worst-case 20 second polling period or even the 10 second expected wait.)
You can change the polling period from 4s by calling this function. The new polling period takes effect when the next discovery message is sent at the end of the current polling period.
period | the requested polling period; a minimum of 0.1s is enforced; 4s is the default (recommended). |
int o2_status | ( | const char * | service | ) |
Check the status of the service.
service | the name of the service |
o2_status(service) > O2_FAIL
, o2_status(service) >= 0
, or o2_status(service) >= O2_LOCAL_NOTIME
.o2_status(service) >= O2_LOCAL
. Note that status can change over time, e.g. the status of a remote service will be O2_FAIL until the service is discovered. It will then change to O2_REMOTE_NOTIME until both the sender and receiver achieve clock synchronization and share their synchronized status, and finally the status will become O2_REMOTE.In the cases with no clock sync, it is safe to send an immediate message with timestamp = 0, but non-zero timestamps are meaningless because either the sending process has no way to obtain a valid timestamp or the receiver has no way to schedule delivery according to a timestamp.
Messages to services are dropped if the service has not been discovered. Timestamped messages (timestamp != 0) are dropped if the sender and receiver are not clock-synchronized. (o2_status(service) >= O2_LOCAL
).
A special case is with BRIDGE
and OSC
services. In these cases, the O2 process offering the service can either schedule the messages locally, sending them according to the timestamp (and suffering some network latency), or if the destination process is synchronized, messages can be forwarded immediately for more precise scheduling at their final destination. O2 does not provide any way for clients/users to determine which of these methods is in effect, and in the case of messages being forwarded by an intermediary O2 process, the originator of the message cannot determine whether the service is offered by an O2 server on the local network, by an OSC server, or through a bridge to another network such as Bluetooth. The status at the originator will be simply O2_REMOTE or O2_REMOTE_NOTIME.
o2_time o2_time_get | ( | ) |
Get the estimated synchronized global O2 time.
This function returns a valid value either after you call o2_clock_set(), making the local clock the master clock for the O2 application, or after O2 has finished discovering and synchronizing with the master clock. Until then, -1 is returned.
The clock accuracy depends upon network latency, how often o2_poll() is called, and other factors, but
o2_sched_ptr o2_active_sched |
Current scheduler.
When a timed message is delivered by a scheduler, o2_active_sched is set to pount to the scheduler. A handler that constructs and schedules a message can use this pointer to continue using the same scheduler.
const char* o2_application_name |
name of the application
A collection of cooperating O2 processes forms an application. Applications must have unique names. This allows more than one application to exist within a single network without conflict. For example, there could be two applications, "joe" and "sue", each with services named "synth." Since the application names are different, joe's messages to the synth service go to joe's synth and not to sue's synth.
Do not set, modify or free this variable! Consider it to be read-only. It is managed by O2 using o2_initialize() and o2_finish().
o2_sched o2_gtsched |
Scheduler that schedules according to global (master) clock time.
Scheduling on this scheduler (including sending timed messages) will only work after clock synchronization is obtained. Until then, timed message sends will fail and attempts to o2_schedule() will fail.
o2_sched o2_ltsched |
Scheduler that schedules according to local clock time.
It may be necessary to schedule events before clock synchronization with the master clock, or you may want to schedule local processing that ignores any changes in clock time or clock speed needed to stay synchronized with the master clock (even though these should be small). For example, O2 uses the local time scheduler to schedule the clock synchronization protocol, which of course must run before clock synchronization is obtained.
In these cases, you should schedule messages using o2_ltsched.
int o2_stop_flag |
set this flag to stop o2_run()
Some O2 applications will initialize and call o2_run(), which is a simple loop that calls o2_poll(). To exit the loop, set o2_stop_flag to #TRUE