383 lines
12 KiB
C
383 lines
12 KiB
C
#include"node.h"
|
|
|
|
#include<eebie/writer.h>
|
|
#include<eebie/reader.h>
|
|
#include<assert.h>
|
|
#include<stdio.h>
|
|
#include"node_internal.h"
|
|
#include<endian.h>
|
|
|
|
#define ID_PROJECT 0x10000000
|
|
#define ID_DURATION 0x4000
|
|
#define ID_TIME 0x4001
|
|
#define ID_KEYFRAMES 0x200000
|
|
#define ID_CHIVAL 0x80
|
|
#define ID_CHIVAL_TYPE 0x81
|
|
#define ID_LINK 0x82
|
|
#define ID_DATA 0x83
|
|
#define ID_EXTRAPOLATION_MODE 0x4002
|
|
#define ID_EXTRAPOLATION_PARAM 0x4003
|
|
#define ID_NODE 0x84
|
|
#define ID_NODE_TYPE 0x85
|
|
#define ID_LIFESPAN_START 0x4004
|
|
#define ID_LIFESPAN_END 0x4005
|
|
|
|
static void put_chival(CHiNodeGraph *ng, EBMLWriter *ebml, CHiValue val) {
|
|
ebml_writer_push(ebml, ID_CHIVAL);
|
|
ebml_writer_put(ebml, ID_CHIVAL_TYPE, EBML_UNSIGNED_INTEGER, (EBMLPrimitive) {.uInt = val.type});
|
|
|
|
if(val.linked.to) {
|
|
size_t index;
|
|
for(index = 0; index < ng->count; index++) {
|
|
if(ng->nodes[index] == val.linked.to) {
|
|
break;
|
|
}
|
|
}
|
|
assert(index < ng->count);
|
|
|
|
ebml_writer_put(ebml, ID_LINK, EBML_UNSIGNED_INTEGER, (EBMLPrimitive) {.uInt = (index << 16) | (val.linked.idx)});
|
|
}
|
|
|
|
if(val.type == CUTIHI_VAL_TEXT) {
|
|
ebml_writer_put(ebml, ID_DATA, EBML_BINARY, (EBMLPrimitive) {.binary = {.ptr = val.data.text, .length = strlen(val.data.text)}});
|
|
} else if(val.type == CUTIHI_VAL_VEC4) {
|
|
ebml_writer_put(ebml, ID_DATA, EBML_BINARY, (EBMLPrimitive) {.binary = {.ptr = val.data.vec4, .length = sizeof(val.data.vec4)}});
|
|
} else if(val.type == CUTIHI_VAL_KEYED) {
|
|
size_t index;
|
|
for(index = 0; index < ng->keyframesList.count; index++) {
|
|
if(ng->keyframesList.keyframes[index] == val.data.keyed) {
|
|
break;
|
|
}
|
|
}
|
|
assert(index < ng->count);
|
|
|
|
ebml_writer_put(ebml, ID_DATA, EBML_UNSIGNED_INTEGER, (EBMLPrimitive) {.uInt = index});
|
|
}
|
|
ebml_writer_pop(ebml);
|
|
}
|
|
|
|
static size_t ebml_write_callback(EBMLWriter *ebml, const void *buf, size_t length) {
|
|
CHiSaveWriter writer = ((void**) ebml->ud)[0];
|
|
void *ud = ((void**) ebml->ud)[1];
|
|
return writer(ud, buf, length);
|
|
}
|
|
static void *ebml_alloc_callback(EBMLWriter *ebml, void *buf, size_t length) {
|
|
return realloc(buf, length);
|
|
}
|
|
|
|
CUTIVIS int CHi_NodeGraphSave(CHiNodeGraph *ng, CHiSaveWriter writer, CHiSaveCustomNodeData customNodeData, void *ud) {
|
|
EBMLWriter ebml;
|
|
ebml_writer_init(&ebml);
|
|
|
|
void *ebmlud[2] = {writer, ud};
|
|
|
|
ebml.ud = ebmlud;
|
|
ebml.eventWrite = ebml_write_callback;
|
|
ebml.eventAlloc = ebml_alloc_callback;
|
|
|
|
ebml_writer_push(&ebml, 0x1A45DFA3);
|
|
ebml_writer_put(&ebml, 0x4286, EBML_UNSIGNED_INTEGER, (EBMLPrimitive) {.uInt = 1});
|
|
ebml_writer_put(&ebml, 0x4282, EBML_STRING, (EBMLPrimitive) {.string = "cuticle"});
|
|
ebml_writer_pop(&ebml);
|
|
|
|
ebml_writer_push(&ebml, ID_PROJECT);
|
|
if(ng->duration != -1) {
|
|
ebml_writer_put(&ebml, ID_DURATION, EBML_FLOAT4, (EBMLPrimitive) {.flt4 = ng->duration});
|
|
}
|
|
ebml_writer_put(&ebml, ID_TIME, EBML_FLOAT4, (EBMLPrimitive) {.flt4 = ng->time});
|
|
|
|
for(size_t i = 0; i < ng->keyframesList.count; i++) {
|
|
CHiKeyframes *kfs = ng->keyframesList.keyframes[i];
|
|
|
|
ebml_writer_push(&ebml, ID_KEYFRAMES);
|
|
//ebml_writer_put(&ebml, ID_CHIVAL_TYPE, EBML_UNSIGNED_INTEGER, (EBMLPrimitive) {.uInt = kfs->type});
|
|
|
|
for(size_t k = 0; k < kfs->count; k++) {
|
|
ebml_writer_put(&ebml, ID_TIME, EBML_FLOAT4, (EBMLPrimitive) {.flt4 = kfs->times[k]});
|
|
put_chival(ng, &ebml, (CHiValue) {.type = kfs->type, .linked = {}, .data = kfs->values[k]});
|
|
}
|
|
|
|
ebml_writer_put(&ebml, ID_EXTRAPOLATION_MODE, EBML_UNSIGNED_INTEGER, (EBMLPrimitive) {.uInt = kfs->extrapolationMode});
|
|
ebml_writer_put(&ebml, ID_EXTRAPOLATION_PARAM, EBML_BINARY, (EBMLPrimitive) {.binary = {.ptr = &kfs->extrapolationParameter, sizeof(kfs->extrapolationParameter)}});
|
|
ebml_writer_pop(&ebml);
|
|
}
|
|
|
|
for(size_t i = 0; i < ng->count; i++) {
|
|
CHiPubNode *node = ng->nodes[i];
|
|
|
|
ebml_writer_push(&ebml, ID_NODE);
|
|
ebml_writer_put(&ebml, ID_NODE_TYPE, EBML_BINARY, (EBMLPrimitive) {.binary = {.ptr = &node->type, sizeof(node->type)}});
|
|
|
|
if(node->lifespan.start != 0) {
|
|
ebml_writer_put(&ebml, ID_LIFESPAN_START, EBML_FLOAT4, (EBMLPrimitive) {.flt4 = node->lifespan.start});
|
|
}
|
|
if(node->lifespan.end != 0) {
|
|
ebml_writer_put(&ebml, ID_LIFESPAN_END, EBML_FLOAT4, (EBMLPrimitive) {.flt4 = node->lifespan.end});
|
|
}
|
|
|
|
for(size_t sink = 0; sink < node->sinkCount; sink++) {
|
|
put_chival(ng, &ebml, node->sinks[sink]);
|
|
}
|
|
|
|
if(customNodeData) {
|
|
customNodeData(ud, &ebml, node);
|
|
}
|
|
ebml_writer_pop(&ebml);
|
|
}
|
|
ebml_writer_pop(&ebml);
|
|
|
|
while(!ebml_writer_flush(&ebml));
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct LoadState {
|
|
uint8_t *accum;
|
|
size_t accumSize;
|
|
|
|
CHiNodeGraph *ng;
|
|
|
|
void *ud;
|
|
CHiLoadCustomNodeData customNodeData;
|
|
|
|
// Filled in by callbacks
|
|
CHiValue activeVal;
|
|
|
|
// Filled in by callbacks
|
|
size_t activeNodeNextSinkIdx;
|
|
CHiPubNode *activeNode;
|
|
};
|
|
|
|
static EBMLElementType ebml_enter_callback(EBMLReader *ebml, uint64_t id, uint64_t length) {
|
|
struct LoadState *state = ebml->ud;
|
|
|
|
if(id == ID_KEYFRAMES) {
|
|
state->ng->keyframesList.keyframes = realloc(state->ng->keyframesList.keyframes, sizeof(*state->ng->keyframesList.keyframes) * (++state->ng->keyframesList.count));
|
|
state->ng->keyframesList.keyframes[state->ng->keyframesList.count - 1] = calloc(1, sizeof(CHiKeyframes));
|
|
} else if(id == ID_CHIVAL) {
|
|
memset(&state->activeVal, 0, sizeof(state->activeVal));
|
|
}
|
|
|
|
return (id == ID_PROJECT || id == ID_KEYFRAMES || id == ID_CHIVAL || id == ID_NODE || id == 0x5000) ? EBML_TREE : EBML_BINARY;
|
|
}
|
|
|
|
static void ebml_data_callback(EBMLReader *ebml, const uint8_t *data, size_t length) {
|
|
struct LoadState *state = ebml->ud;
|
|
|
|
state->accum = realloc(state->accum, state->accumSize + length);
|
|
memcpy(state->accum + state->accumSize, data, length);
|
|
state->accumSize += length;
|
|
}
|
|
|
|
static void ebml_exit_callback(EBMLReader *ebml) {
|
|
struct LoadState *state = ebml->ud;
|
|
|
|
uint64_t parent = ebml->currentDepth >= 2 ? ebml->idStack[ebml->currentDepth - 2] : 0;
|
|
uint64_t id = ebml->idStack[ebml->currentDepth - 1];
|
|
|
|
if(id == ID_NODE) {
|
|
state->activeNode = NULL;
|
|
} else if(parent == 0x5000 && state->customNodeData) {
|
|
assert(state->activeNode == state->ng->nodes[state->ng->count - 1]);
|
|
|
|
state->customNodeData(state->ud, state->ng->count - 1, id, state->accum, state->accumSize);
|
|
} else if(parent == ID_NODE) {
|
|
if(id == ID_NODE_TYPE) {
|
|
assert(state->accumSize <= 8);
|
|
|
|
uint64_t type = 0;
|
|
memcpy((char*) &type + sizeof(type) - state->accumSize, state->accum, state->accumSize);
|
|
|
|
CHiPubNode *n = NULL;
|
|
|
|
if(type == CUTIHI_T('CPre','view')) {
|
|
n = CHi_Preview();
|
|
} else if(type == CUTIHI_T('CMix','er ')) {
|
|
n = CHi_Mixer();
|
|
} else if(type == CUTIHI_T('CTex','t ')) {
|
|
n = CHi_Text();
|
|
} else if(type == CUTIHI_T('CTim','e ')) {
|
|
n = CHi_Time();
|
|
} else if(type == CUTIHI_T('CMod','ulat')) {
|
|
n = CHi_Modulate();
|
|
} else if(type == CUTIHI_T('CCns','tCol')) {
|
|
n = CHi_ConstantSample();
|
|
} else if(type == CUTIHI_T('CEmb','ed ')) {
|
|
n = CHi_Embed();
|
|
} else if(type == CUTIHI_T('CIma','ge ')) {
|
|
n = CHi_Image();
|
|
} else if(type == CUTIHI_T('CWin','dow ')) {
|
|
n = CHi_Window();
|
|
} else if(type == CUTIHI_T('CInA','udio')) {
|
|
n = CHi_Microphone();
|
|
} else if(type == CUTIHI_T('CExp','Wave')) {
|
|
n = CHi_ExportWav();
|
|
} else if(type == CUTIHI_T('CMov','ie ')) {
|
|
n = CHi_Movie();
|
|
} else if(type == CUTIHI_T('CEnc','GVP8')) {
|
|
n = CHi_EncodeVP8();
|
|
} else if(type == CUTIHI_T('CEnc','GVP9')) {
|
|
n = CHi_EncodeVP9();
|
|
} else if(type == CUTIHI_T('CExp','Webm')) {
|
|
n = CHi_MuxWebm();
|
|
} else if(type == CUTIHI_T('CKey','hook')) {
|
|
n = CHi_Keyhook();
|
|
} else if(type == CUTIHI_T('CKey','hook')) {
|
|
n = CHi_Keyhook();
|
|
} else if(type == CUTIHI_T('CEnc','Opus')) {
|
|
n = CHi_EncodeOpus();
|
|
} else if(type == CUTIHI_T('CWeb','Cam ')) {
|
|
n = CHi_Camera();
|
|
} else if(type == CUTIHI_T('CCmp','nScl')) {
|
|
n = CHi_ComponentScale();
|
|
} else if(type == CUTIHI_T('CEnc','H264')) {
|
|
n = CHi_EncodeH264();
|
|
} else if(type == CUTIHI_T('CStr','RTMP')) {
|
|
n = CHi_StreamRTMP();
|
|
} else if(type == CUTIHI_T('CEnc','AACL')) {
|
|
n = CHi_EncodeAAC();
|
|
} else if(type == CUTIHI_T('CExp','Mkv ')) {
|
|
n = CHi_MuxMatroska();
|
|
}
|
|
|
|
n->ng = state->ng;
|
|
|
|
if(!n) {
|
|
CHi_NodeGraphReset(state->ng);
|
|
assert(0 && "Unknown node type.");
|
|
}
|
|
|
|
state->ng->nodes = realloc(state->ng->nodes, sizeof(*state->ng->nodes) * (++state->ng->count));
|
|
state->ng->nodes[state->ng->count - 1] = n;
|
|
|
|
state->activeNodeNextSinkIdx = 0;
|
|
state->activeNode = n;
|
|
} else if(id == ID_LIFESPAN_START) {
|
|
state->activeNode->lifespan.start = *(float*) state->accum;
|
|
} else if(id == ID_LIFESPAN_END) {
|
|
state->activeNode->lifespan.end = *(float*) state->accum;
|
|
} else if(id == ID_CHIVAL) {
|
|
if(state->activeNodeNextSinkIdx >= state->activeNode->sinkCount) {
|
|
state->activeNode->sinks = realloc(state->activeNode->sinks, sizeof(*state->activeNode->sinks) * (state->activeNode->sinkCount = (state->activeNodeNextSinkIdx + 1)));
|
|
}
|
|
state->activeNode->sinks[state->activeNodeNextSinkIdx++] = state->activeVal;
|
|
}
|
|
} else if(parent == ID_CHIVAL) {
|
|
if(id == ID_CHIVAL_TYPE) {
|
|
state->activeVal.type = *(uint8_t*) state->accum;
|
|
} else if(id == ID_LINK) {
|
|
assert(state->accumSize <= 8);
|
|
|
|
uint64_t data = 0;
|
|
memcpy((char*) &data + sizeof(data) - state->accumSize, state->accum, state->accumSize);
|
|
data = htobe64(data);
|
|
|
|
uint16_t srcIdx = data & 0xFFFF;
|
|
uint64_t nodeIdx = data >> 16;
|
|
|
|
// linked.to is supposed to be a pointer but not all nodes are
|
|
// available at this stage, so we'll fix it up after the EBML
|
|
// parsing finishes
|
|
state->activeVal.linked.to = (void*) (nodeIdx + 1);
|
|
state->activeVal.linked.idx = srcIdx;
|
|
} else if(id == ID_DATA) {
|
|
if(state->activeVal.type == CUTIHI_VAL_TEXT) {
|
|
state->activeVal.data.text = calloc(1, state->accumSize + 1);
|
|
memcpy(state->activeVal.data.text, state->accum, state->accumSize);
|
|
} else if(state->activeVal.type == CUTIHI_VAL_VEC4) {
|
|
assert(state->accumSize <= 16 && state->accumSize % 4 == 0);
|
|
|
|
memcpy(state->activeVal.data.vec4, state->accum, state->accumSize);
|
|
} else if(state->activeVal.type == CUTIHI_VAL_KEYED) {
|
|
assert(state->accumSize <= 8);
|
|
|
|
uint64_t idx = 0;
|
|
memcpy((char*) &idx + sizeof(idx) - state->accumSize, state->accum, state->accumSize);
|
|
idx = htobe64(idx);
|
|
|
|
// keyed is supposed to be a pointer but keyframes may not
|
|
// be available at this stage, so we'll fix it up after the
|
|
// EBML parsing finishes
|
|
state->activeVal.data.keyed = (void*) idx;
|
|
} else {
|
|
assert(0 && "Cannot load CHiValue data.");
|
|
}
|
|
}
|
|
} else if(parent == ID_KEYFRAMES) {
|
|
CHiKeyframes *kfs = state->ng->keyframesList.keyframes[state->ng->keyframesList.count - 1];
|
|
if(id == ID_TIME) {
|
|
kfs->times = realloc(kfs->times, sizeof(*kfs->times) * (++kfs->count));
|
|
kfs->values = realloc(kfs->values, sizeof(*kfs->values) * kfs->count);
|
|
|
|
kfs->times[kfs->count - 1] = *(float*) state->accum;
|
|
} else if(id == ID_CHIVAL) {
|
|
assert(state->activeVal.linked.to == NULL);
|
|
|
|
kfs->values[kfs->count - 1] = state->activeVal.data;
|
|
} else if(id == ID_EXTRAPOLATION_MODE) {
|
|
kfs->extrapolationMode = *(uint8_t*) state->accum;
|
|
} else if(id == ID_EXTRAPOLATION_PARAM) {
|
|
assert(state->accumSize == 16);
|
|
|
|
memcpy(&kfs->extrapolationParameter, state->accum, 16);
|
|
}
|
|
}
|
|
|
|
state->accumSize = 0;
|
|
}
|
|
|
|
CUTIVIS int CHi_NodeGraphLoad(CHiNodeGraph *ng, CHiLoadReader reader, CHiLoadCustomNodeData customNodeData, void *ud) {
|
|
CHi_NodeGraphReset(ng);
|
|
|
|
ng->time = 0;
|
|
ng->duration = -1;
|
|
|
|
EBMLReader ebml;
|
|
ebml_reader_init(&ebml);
|
|
|
|
struct LoadState state = {.ng = ng, .ud = ud, .customNodeData = customNodeData};
|
|
ebml.ud = &state;
|
|
|
|
ebml.eventEnterElement = ebml_enter_callback;
|
|
ebml.eventDataChunk = ebml_data_callback;
|
|
ebml.eventExitElement = ebml_exit_callback;
|
|
|
|
size_t i = 0;
|
|
uint8_t buf[4096];
|
|
while(1) {
|
|
size_t readBytes = reader(ud, buf + i, sizeof(buf) - i);
|
|
|
|
i += readBytes;
|
|
|
|
size_t fedBytes = ebml_reader_feed(&ebml, buf, i);
|
|
|
|
memmove(buf, buf + fedBytes, sizeof(buf) - fedBytes);
|
|
|
|
i -= fedBytes;
|
|
|
|
if(readBytes == 0 && fedBytes == 0) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Fix-up links/adjacencies
|
|
for(size_t i = 0; i < ng->count; i++) {
|
|
CHiPubNode *n = ng->nodes[i];
|
|
for(size_t s = 0; s < n->sinkCount; s++) {
|
|
if(n->sinks[s].linked.to) {
|
|
n->sinks[s].linked.to = ng->nodes[(uintptr_t) n->sinks[s].linked.to - 1];
|
|
|
|
adjacency_add(n->sinks[s].linked.to, n);
|
|
}
|
|
|
|
if(n->sinks[s].type == CUTIHI_VAL_KEYED) {
|
|
n->sinks[s].data.keyed = ng->keyframesList.keyframes[(uintptr_t) n->sinks[s].data.keyed];
|
|
}
|
|
}
|
|
}
|
|
|
|
update_keyed_values(ng);
|
|
|
|
return 0;
|
|
}
|