Jitter のネットワーク仕様

jit.net のプロトコルに関する詳細

この補遺では、jit.net.send オブジェクトによって送信されるデータのフォーマットについて述べます。このオブジェクトは、アトリビュートで指定された IP と ポートによって、ホストと TCP コネクションを確立しようとします。したがって、何らかのデータを受信しようとするオブジェクトは、自分をホストとして設定し、入力される TCP コネクションで待ち受けていなければなりません。一度接続が確立されると、データを受信することができるようになります。データはチャンクのストリームとして送信されます。最初に受信するものはチャンクヘッダです。これには 32 ビットのチャンク ID と 次に受信するチャンクの データサイズを 32 ビット整数で表した値が含まれます。チャンク ID は、パケットの種類によって、次に示すような4文字によるシンボルの中の1つになります。

#define JIT_MATRIX_PACKET_ID 'JMTX' #define JIT_MATRIX_LATENCY_PACKET_ID 'JMLP' #define JIT_MESSAGE_PACKET_ID 'JMMP'

このチャンクヘッダは、次のような C 構造体によって表現することができます。

typedef struct _jit_net_packet_header { long id; long size; //入力されるパケットのサイズ } t_jit_net_packet_header;

チャンクがマトリックスパケットである場合、次に受信するデータは 288 バイトのヘッダで、その内容は次のようなものになります。

ID (id) 'JMTX'
サイズ (Size) 288 (32 ビット 整数、ヘッダのサイズ)
プレーン数 ( Planecount) 32-bit 整数
型 (Type) 32ビット整数
0 : char、1 : long、 2 : float32、 3:float64
ディメンション数 (Dimcount) 32-bit 整数
ディメンションのサイズ (Dim) 32個の32ビット整数を持つ配列
dim ストライド (Dimstride) 32個の32ビット整数を持つ配列
データサイズ (Datasize) 32ビット整数、入力されるデータバッファのサイズ
タイム (Time) 64ビット double 精度の float

このチャンクは、次のような C 構造体で表現することができます。

typedef struct _jit_net_packet_matrix { long id; long size; long planecount; long type; //0=char,1=long,2=float32,3=float64 long dimcount; long dim[JIT_MATRIX_MAX_DIMCOUNT]; long dimstride[JIT_MATRIX_MAX_DIMCOUNT]; long datasize; double time; } t_jit_net_packet_matrix;

このヘッダの次に受け取るデータはマトリックスデータになります。マトリックスデータのサイズは上記のヘッダの中で渡されます。データを使用する場合には、くれぐれもヘッダで受信した dimstride に注意して下さい。上記のヘッダの time フィールドには、送信側コンピュータからの送信時間が設定されます。jit.net.send はサーバに対し、応答としてサーバ自身のタイミングデータを送り返すことを要求します。このデータは伝達時間の遅れ(レイテンシ)を推定するために使用されます。jit.net.send が要求するレイテンシチャンクの詳細なデータは次のようなものです。

id 'JMLP'
client_time_original 64ビットの double, 受信したマトリックスヘッダパケットの time 値
server_time_before_data 64ビットの double、パケットヘッダを受信したときのサーバの time 値
server_time_after_data 64ビットの double,、パケットの処理が終了したときのサーバの time 値。これが使用されます。

サーバがデータを受信したときの時刻(server time before)とサーバが処理を終了した時刻(server time after)の差は、サーバがデータを受信し、送信を開始するまでにかかる時間を表しています。jit.net.send はこの情報の送信や要求をミリ秒単位で行ないます。このタイミング情報が送信側コンピュータで受信されると、サーバは現在の時刻に注目し、往復にかかった時間を計算して、この往復時間の 1/2 に、サーバの処理時間の 1/2 を加算してレイテンシを推定します。A から B までの移動に要する時間と B から A までの移動に要する時間が同じであれば、この推定は正確な値になりますが、ネットワークのトポロジには非常に複雑なこともあり、多くの場合 A から B への移動にかかる時間が B から A への移動にかかる時間と等しくないという状況が生じます。2つのコンピュータ間が小さな LAN で直接に接続されているようなシンプルな環境では、推定値はかなり正確なものになります

送信されるパケットの型の最後はメッセージパケットです。メッセージパケットのサイズは、最初のヘッダパケットの中で送信されます。メッセージパケットでは、標準の GIMME メッセージ (t_symbol *s, long ac, t_atom *av) が連続して送信されますが、先頭には、32 ビット整数で表されるメッセージ全体のサイズのバイト数が置かれ、その次にはもう1つの 32 ビット整数によって、atom の引数の数が続きます。その後に、先頭のシンボルが存在する場合には、そのシンボルを最初としてメッセージの atom 自身が送信されます。それぞれの atom はメモリの中で次のような形をとります。まず最初に atom の型を表す文字(char)が置かれます。これが "s"であれば、atom の型は シンボルに、"l"であれば long に、"f" であれば float になります。long と float の atom では、続く 4 バイトがその atom の値になります。シンボルの atom では、シンボル文字列の最後に NULL 文字が追加されます。次の C の関数は、データポインタとして渡された連続したメッセージの仕分けを行なうものです。

void gimme_deserialize(char *data, t_symbol **s, long *ac, t_atom **av) { char *curr = data; float *currf; long *currl,i; long datasize = BE_I32(*((long *)curr)); curr += sizeof(long); *ac = BE_I32(*(long *)(curr)); curr += sizeof(long); *av = (t_atom *)sysmem_newptr(sizeof(t_atom)*(*ac)); if (*curr == ATOM_SERIALIZATION_SYMBOL_CODE) { curr++; *s = gensym(curr); while (*(++curr) != '\0') ; curr++; } else *s = 0L; for (i=0;i< *ac;i++) switch (*curr++) { case ATOM_SERIALIZATION_SYMBOL_CODE: (*av)[i].a_type = A_SYM; (*av)[i].a_w.w_sym = gensym(curr); while (*(++curr) != '\0') ; curr++; break; case ATOM_SERIALIZATION_FLOAT_CODE: (*av)[i].a_type = A_FLOAT; (*av)[i].a_w.w_float = BE_F32(*((float *)curr)); curr += sizeof(float); break; case ATOM_SERIALIZATION_LONG_CODE: (*av)[i].a_type = A_LONG; (*av)[i].a_w.w_long = BE_I32(*((long *)curr)); curr += sizeof(long); break; } }