Compare commits

..

18 Commits

Author SHA1 Message Date
mid
b1a1b9a1eb Architectural updates
1. Introduce image UUIDs, so nodes may check for source updates.
2. Flip over design. Now, CHiPubNodes contain private data, instead of
CHiPubNodes being contained within private data. Each node must cache
its source data if it wants to conditionally run.
2025-10-15 09:45:38 +03:00
mid
cc70d9138a Don't fuckin call SetImage from the not UI thread wtf 2025-10-15 09:42:06 +03:00
mid
28e7fa3365 Fix vpx encoding 2025-10-12 16:57:58 +03:00
mid
8b78f0cdf7 fix preview resizing 2025-10-12 12:19:46 +03:00
mid
542b271c6d fix window_x11 2025-10-12 12:10:28 +03:00
mid
9ad84c447b do unaligned load 2025-10-12 11:50:58 +03:00
mid
3993163d6d Start Windows compatibility
screen_capture_lite turned out to be pretty broken and so I brought back
my old X11 implementation for the Window node, for Unices only.
Hopefully SCL actually works on Windows because lemme tell you, I do not
want to go knee-deep in that.

Additionally, SAIL was replaced with stb_image because I couldn't get
SAIL to build under MinGW.
2025-10-12 11:23:49 +03:00
mid
d77ae15b46 MKV output bugfixes 2025-10-12 11:19:17 +03:00
mid
fb6b0dd492 prepare for trouble 2025-10-12 11:09:06 +03:00
mid
aff60df9da Use alpha in image viewer (checkered background) 2025-10-05 23:29:40 +03:00
mid
c7468d0944 Use new window API in frame.cpp 2025-10-05 23:29:14 +03:00
mid
1651beddf4 Better real-time parameters for VP9
Still not practically real-time, but good enough for 720p @ 30fps
2025-10-05 23:27:05 +03:00
mid
0384176de6 Replace X11-based screen capture with cross-platform library
Unfortunately this brings back the C++ requirement, but later on this
will happen anyway, what with me wanting some CV funsies.
2025-10-05 23:26:33 +03:00
mid
e510d92b17 Some error recovery 2025-10-05 23:24:12 +03:00
mid
c91115dad4 Numerous bug fixes, including strides 2025-10-05 23:23:40 +03:00
mid
b6e6713860 Move to EBML-based project file format 2025-10-05 23:21:21 +03:00
mid
33c9207947 Bug fixes 2025-10-05 23:09:32 +03:00
mid
50c9a7a6ff Switch from WebM to full Matroska muxer 2025-03-31 21:37:04 +03:00
26 changed files with 2313 additions and 918 deletions

3
.gitignore vendored
View File

@@ -3,3 +3,6 @@ tests
*.webm
/cuticle
*.so
*.dll
*.exe

View File

@@ -1,6 +1,26 @@
CXXFLAGS := -D_POSIX_C_SOURCE=200809L -Wno-narrowing -march=native -flto -Wall -fvisibility=hidden -fPIC -msse4 -I./ -I/usr/local/include/sail `pkg-config --cflags pango opus libv4l2` '-Wl,-rpath,$$ORIGIN' -Wno-multichar
LDFLAGS := -lwebm -lpng -lvpx -lsail -lsail-manip `pkg-config --libs pango opus libv4l2` -lportaudio -lXtst -lrtmp -lfdk-aac
CXXFLAGS := -D_DEFAULT_SOURCE -D_POSIX_C_SOURCE=200809L -Wno-narrowing -march=native -flto -Wall -fvisibility=hidden -fPIC -msse4 -I./ '-Wl,-rpath,$$ORIGIN' -Wno-multichar
LDFLAGS := -lwebm -lpng -lvpx -lportaudio -lrtmp -lfdk-aac -leebie -lscreen_capture_lite_shared
ifneq ($(MINGW),0)
CXXFLAGS := `pkg-config --cflags pango opus libv4l2` -I/usr/local/bin -I/usr/local/include/sail $(CXXFLAGS)
LDFLAGS := $(LDFLAGS) -lXtst `pkg-config --libs pango opus libv4l2` -lstdc++
WXCFLAGS := `wx-config --cflags base,adv,core,aui`
WXLDFLAGS := `wx-config --libs base,adv,core,aui`
RELAY_C := relay_x11.c
WINDOW_C := window_x11.cpp
LIBCUTIHI := libcutihi.so
else
CXXFLAGS := `x86_64-w64-mingw32-pkg-config --cflags pangoft2 freetype2 opus` $(CXXFLAGS)
LDFLAGS := $(LDFLAGS) -pthread -lwinmm -lwebm -lole32 -lsetupapi -lws2_32 -lopus -lssp `x86_64-w64-mingw32-pkg-config --libs pangoft2` -lstdc++
WXCFLAGS := `wx-config --host=x86_64-w64-mingw32 --prefix=/usr/x86_64-w64-mingw32/ --cflags base,adv,core,aui`
WXLDFLAGS := `wx-config --host=x86_64-w64-mingw32 --prefix=/usr/x86_64-w64-mingw32/ --libs base,adv,core,aui`
RELAY_C := relay_win.c
WINDOW_C := window_scl.cpp
CC = x86_64-w64-mingw32-gcc
CXX = x86_64-w64-mingw32-g++
LIBCUTIHI := libcutihi.dll
endif
ifneq ($(RELEASE),0)
CXXFLAGS := $(CXXFLAGS) -O0 -gdwarf-2 -DMTR_ENABLED
@@ -8,22 +28,29 @@ else
CXXFLAGS := $(CXXFLAGS) -O3 -fopenmp -DMTR_ENABLED
endif
ifneq ($(SANITIZE),0)
else
CXXFLAGS := -fsanitize=address $(CXXFLAGS)
endif
all:
$(CC) $(CXXFLAGS) -std=c99 -shared -c -o node.o hi/node.c $(LDFLAGS)
$(CC) $(CXXFLAGS) -std=c99 -shared -c -o window.o hi/window.c $(LDFLAGS)
$(CXX) $(CXXFLAGS) -std=c++17 -shared -c -o window.o hi/$(WINDOW_C) $(LDFLAGS)
$(CC) $(CXXFLAGS) -std=c99 -shared -c -o microphone.o hi/microphone.c $(LDFLAGS)
$(CC) $(CXXFLAGS) -std=c99 -shared -c -o mode.o hi/mode.c $(LDFLAGS)
$(CC) $(CXXFLAGS) -std=c99 -shared -c -o img.o hi/img.c $(LDFLAGS)
$(CXX) $(CXXFLAGS) -std=c++11 -shared -c -o webmdec.o hi/webmdec.cpp $(LDFLAGS)
$(CXX) $(CXXFLAGS) -std=c++11 -shared -c -o webmenc.o hi/webmenc.cpp $(LDFLAGS)
$(CC) $(CXXFLAGS) -std=c++11 -shared -c -o vpxenc.o hi/vpxenc.c $(LDFLAGS)
$(CXX) $(CXXFLAGS) -std=c++17 -shared -c -o webmdec.o hi/webmdec.cpp $(LDFLAGS)
# $(CXX) $(CXXFLAGS) -std=c++17 -shared -c -o webmenc.o hi/webmenc.cpp $(LDFLAGS)
$(CC) $(CXXFLAGS) -std=c99 -shared -c -o vpxenc.o hi/vpxenc.c $(LDFLAGS)
$(CC) $(CXXFLAGS) -std=c99 -shared -c -o opus.o hi/opus.c $(LDFLAGS)
$(CC) $(CXXFLAGS) -std=c99 -shared -c -o webcam.o hi/webcam.c $(LDFLAGS)
$(CC) $(CXXFLAGS) -std=c99 -shared -c -o scale.o hi/relay.c $(LDFLAGS)
$(CC) $(CXXFLAGS) -std=c99 -shared -c -o scale.o hi/$(RELAY_C) $(LDFLAGS)
$(CC) $(CXXFLAGS) -std=c99 -shared -c -o minitrace.o hi/minitrace.c $(LDFLAGS)
$(CC) $(CXXFLAGS) -std=c99 -shared -c -o h264enc.o hi/h264enc.c $(LDFLAGS)
$(CC) $(CXXFLAGS) -std=c99 -shared -c -o rtmp.o hi/rtmp.c $(LDFLAGS)
$(CC) $(CXXFLAGS) -std=c99 -shared -c -o aaclc.o hi/aaclc.c $(LDFLAGS)
$(CC) $(CXXFLAGS) -shared -o libcutihi.so -shared node.o webmdec.o webmenc.o window.o microphone.o mode.o img.o opus.o webcam.o scale.o minitrace.o h264enc.o rtmp.o aaclc.o vpxenc.o $(LDFLAGS)
$(CC) $(CXXFLAGS) -std=c99 -shared -c -o mkv.o hi/mkv.c $(LDFLAGS)
$(CC) $(CXXFLAGS) -std=c99 -shared -c -o serialize.o hi/serialize.c $(LDFLAGS)
$(CC) $(CXXFLAGS) -shared -o $(LIBCUTIHI) node.o webmdec.o window.o microphone.o mode.o img.o opus.o webcam.o scale.o minitrace.o h264enc.o rtmp.o aaclc.o vpxenc.o mkv.o serialize.o $(LDFLAGS)
$(CXX) $(CXXFLAGS) -std=c++11 `wx-config --cflags base,adv,core,aui` -o cuticle ui/main.cpp ui/frame.cpp ui/textctrl.cpp ui/timeline.cpp -L./ -lcutihi $(LDFLAGS) `wx-config --libs base,adv,core,aui`
$(CXX) $(CXXFLAGS) $(WXCFLAGS) -std=c++11 -o cuticle ui/main.cpp ui/frame.cpp ui/textctrl.cpp ui/timeline.cpp -L./ -lcutihi $(LDFLAGS) $(WXLDFLAGS)

75
hi/bs.h
View File

@@ -5,6 +5,9 @@
extern "C" {
#endif
#include<stdlib.h>
#include<string.h>
#define CUTIHI_BS_FLAG_KEY 1
#define CUTIHI_BS_SETUP_PACKET 2
@@ -20,6 +23,78 @@ typedef struct {
CHiBSFrame data[];
} CHiBSFrames;
static inline CHiBSFrames *CHi_BS_Combine(CHiBSFrames *A, const CHiBSFrames *B) {
CHiBSFrames *ret = NULL;
size_t aoffset = 0;
if(A) {
aoffset = A->count;
ret = (CHiBSFrames*) calloc(1, sizeof(*ret) + sizeof(CHiBSFrame) * (A->count + B->count));
ret->count = A->count + B->count;
memcpy(ret->data, A->data, sizeof(*A->data) * A->count);
free(A);
} else {
ret = (CHiBSFrames*) calloc(1, sizeof(*ret) + sizeof(CHiBSFrame) * B->count);
ret->count = B->count;
}
for(size_t i = 0; i < B->count; i++) {
void *copy = malloc(B->data[i].sz);
memcpy(copy, B->data[i].ptr, B->data[i].sz);
ret->data[aoffset + i] = (CHiBSFrame) {
.timestamp = B->data[i].timestamp,
.sz = B->data[i].sz,
.flags = B->data[i].flags,
.ptr = copy
};
}
return ret;
}
static inline CHiBSFrames *CHi_BS_Empty() {
return (CHiBSFrames*) calloc(1, sizeof(CHiBSFrames));
}
static inline void CHi_BS_Pop(CHiBSFrames *bsfs, size_t num) {
for(size_t i = 0; i < num; i++) {
free(bsfs->data[i].ptr);
}
memmove(&bsfs->data[0], &bsfs->data[num], sizeof(bsfs->data[0]) * (bsfs->count - num));
bsfs->count -= num;
}
static inline void CHi_BS_Clear(CHiBSFrames *bsfs) {
CHi_BS_Pop(bsfs, bsfs->count);
}
static inline void CHi_BS_Free(CHiBSFrames *bsfs) {
if(bsfs) {
CHi_BS_Clear(bsfs);
free(bsfs);
}
}
static inline CHiBSFrames *CHi_BS_Grow(CHiBSFrames *bsfs, size_t num) {
size_t oldsz = sizeof(*bsfs) + sizeof(CHiBSFrame) * bsfs->count;
size_t newsz = sizeof(*bsfs) + sizeof(CHiBSFrame) * (bsfs->count + num);
CHiBSFrames *ret = (CHiBSFrames*) realloc(bsfs, newsz);
ret->count += num;
memset((uint8_t*) ret + oldsz, 0, newsz - oldsz);
return ret;
}
typedef struct {
uint16_t width;
uint16_t height;
} CHiVPxBSSettings;
#ifdef __cplusplus
}
#endif

View File

@@ -2,18 +2,23 @@
#include<mm_malloc.h>
#include<string.h>
#include<assert.h>
CUTIVIS CHiImage* CHi_Image_New(uint8_t bpc, uint8_t channels, uint16_t stride, uint16_t width, uint16_t height, void *data) {
CHiImage *img = malloc(sizeof(*img));
CHiImage *img = calloc(1, sizeof(*img));
img->bpc = bpc;
img->channels = channels;
img->stride = stride;
img->width = width;
img->height = height;
if(data) img->data16 = data;
else img->data16 = _mm_malloc(bpc * stride * height, 16);
else img->data16 = _mm_malloc(bpc * stride * height + 16, 16);
img->owned = !data;
img->uuid = CHi_NextUUID();
assert(stride % 16 == 0);
return img;
}
@@ -37,3 +42,8 @@ CUTIVIS void CHi_Restride(const void *oldbuf_, void *newbuf_, uint16_t oldStride
memmove(&newbuf[newStride * row], &oldbuf[oldStride * row], oldStride);
}
}
CUTIVIS size_t CHi_NextUUID() {
static size_t i = 0;
return i++;
}

View File

@@ -1,5 +1,6 @@
#pragma once
#include<stddef.h>
#include<stdint.h>
#include"defs.h"
@@ -18,6 +19,7 @@ typedef struct CHiImage {
uint8_t *data8;
};
uint8_t owned;
size_t uuid;
} CHiImage;
CUTIVIS CHiImage* CHi_Image_New(uint8_t bpc, uint8_t channels, uint16_t stride, uint16_t width, uint16_t height, void *data);
@@ -25,6 +27,8 @@ CUTIVIS void CHi_Image_Free(CHiImage *img);
CUTIVIS void CHi_Restride(const void *oldbuf, void *newbuf, uint16_t oldStride, uint16_t newStride, uint16_t rows);
CUTIVIS size_t CHi_NextUUID();
#ifdef __cplusplus
}
#endif

View File

@@ -219,7 +219,7 @@ _PS_CONST(cephes_log_q2, 0.693359375);
*/
inline __m128 my_movehl_ps(__m128 a, const __m128 b) {
asm (
__asm__ (
"movhlps %2,%0\n\t"
: "=x" (a)
: "0" (a), "x"(b)
@@ -229,7 +229,7 @@ inline __m128 my_movehl_ps(__m128 a, const __m128 b) {
#define _mm_movehl_ps my_movehl_ps
inline __m128 my_cmplt_ps(__m128 a, const __m128 b) {
asm (
__asm__ (
"cmpltps %2,%0\n\t"
: "=x" (a)
: "0" (a), "x"(b)
@@ -237,7 +237,7 @@ inline __m128 my_cmplt_ps(__m128 a, const __m128 b) {
return a;
}
inline __m128 my_cmpgt_ps(__m128 a, const __m128 b) {
asm (
__asm__ (
"cmpnleps %2,%0\n\t"
: "=x" (a)
: "0" (a), "x"(b)
@@ -245,7 +245,7 @@ inline __m128 my_cmpgt_ps(__m128 a, const __m128 b) {
return a;
}
inline __m128 my_cmpeq_ps(__m128 a, const __m128 b) {
asm (
__asm__ (
"cmpeqps %2,%0\n\t"
: "=x" (a)
: "0" (a), "x"(b)

View File

@@ -98,7 +98,7 @@ static int microphone_perform(CHiPubNode *pubn) {
size_t width = roundf(CHi_Time_GetDelta(pubn->ng) * 48000.f);
do {
}while((node->paBufferWriteIdx - node->paBufferReadIdx + pabufsize) % pabufsize < width); // Wait until available
CHiImage *ret = CHi_Image_New(2, 1, 2 * width, width, 1, NULL);
CHiImage *ret = CHi_Image_New(2, 1, (2 * width + 15) & ~15, width, 1, NULL);
if(node->paBufferReadIdx + width >= pabufsize) {
memcpy(ret->data16, node->paBuffer + node->paBufferReadIdx, sizeof(*node->paBuffer) * (pabufsize - node->paBufferReadIdx));
memcpy(ret->data16 + pabufsize - node->paBufferReadIdx, node->paBuffer, sizeof(*node->paBuffer) * (width - pabufsize + node->paBufferReadIdx));

632
hi/mkv.c Normal file
View File

@@ -0,0 +1,632 @@
#include"node.h"
#include<vpx/vpx_encoder.h>
#include<vpx/vp8cx.h>
#include<eebie/writer.h>
#include<assert.h>
#include"mode.h"
#include"img.h"
#include<math.h>
#include<smmintrin.h>
#include<string.h>
#include"minitrace.h"
#include"linearity.h"
#include"yuv.h"
#include<stdio.h>
#ifdef _WIN32
#include<windows.h>
#include<ntsecapi.h>
static int getrandom(void *buf, size_t buflen, unsigned int flags) {
return RtlGenRandom(buf, buflen) ? buflen : -1;
}
#else
#include<sys/random.h>
#endif
#define NALLENSZ 4
static size_t annexb_parse(const uint8_t *src, const uint8_t *srcEnd) {
int zeros = 0;
const uint8_t *src2;
for(src2 = src; src2 != srcEnd; src2++) {
if(*src2 == 0) {
zeros++;
} else if((zeros == 2 || zeros == 3) && *src2 == 1) {
src2 -= zeros;
break;
} else {
zeros = 0;
}
}
return src2 - src;
}
// I want to die.
static void get_dimensions_from_extradata(const uint8_t *src, uint16_t *foundWidth, uint16_t *foundHeight) {
src += 8;
int nal_unit = *(src++);
int profile_idc = *(src++);
int constraints = *(src++);
int level_idc = *(src++);
int constraint_set0_flag = (constraints >> 7) & 1;
int constraint_set1_flag = (constraints >> 6) & 1;
int constraint_set2_flag = (constraints >> 5) & 1;
int constraint_set3_flag = (constraints >> 4) & 1;
int constraint_set4_flag = (constraints >> 3) & 1;
int constraint_set5_flag = (constraints >> 2) & 1;
size_t bitoffset = 0;
#define ZOOOZOOO_BIT(b) do { b = ((src[0] >> (bitoffset = (bitoffset + 7) % 8)) & 1); if(bitoffset == 0) src++; } while(0);
#define ZOOOZOOO_READ(out, n) do { out = 0; int b; for(int i_i = 0; i_i < n; i_i++) { ZOOOZOOO_BIT(b); out |= (b << (n - i_i - 1)); } } while(0);
#define ZOOOZOOO_UE(out) do { int i = 0; int b = 0; while(1) { ZOOOZOOO_BIT(b); if(b == 1 || i >= 32) break; i++; } ZOOOZOOO_READ(out, i); out += (1 << i) - 1; } while(0);
#define ZOOOZOOO_SE(out) do { ZOOOZOOO_UE(out); if(out & 1) { out = (out + 1) / 2; } else { out = -(out / 2); } } while(0);
int seq_parameter_set_id;
ZOOOZOOO_UE(seq_parameter_set_id);
if(profile_idc == 100 || profile_idc == 110 || profile_idc == 122 || profile_idc == 244 || profile_idc == 44 || profile_idc == 83 || profile_idc == 86 || profile_idc == 118) {
int chroma_format_idc;
ZOOOZOOO_UE(chroma_format_idc);
if(chroma_format_idc == 3) {
int residual_color_transform_flag;
ZOOOZOOO_BIT(residual_color_transform_flag);
}
int bit_depth_luma_minus8;
ZOOOZOOO_UE(bit_depth_luma_minus8);
int bit_depth_chroma_minus8;
ZOOOZOOO_UE(bit_depth_chroma_minus8);
int qpprime_y_zero_transform_bypass_flag;
ZOOOZOOO_BIT(qpprime_y_zero_transform_bypass_flag);
int seq_scaling_matrix_present_flag;
ZOOOZOOO_BIT(seq_scaling_matrix_present_flag);
if(seq_scaling_matrix_present_flag) {
int i = 0;
int lim = chroma_format_idc != 3 ? 8 : 12;
for(i = 0; i < lim; i++) {
int seq_scaling_list_present_flag;
ZOOOZOOO_BIT(seq_scaling_list_present_flag);
if(seq_scaling_list_present_flag) {
int sizeOfScalingList = (i < 6) ? 16 : 64;
int lastScale = 8;
int nextScale = 8;
int j = 0;
for(j = 0; j < sizeOfScalingList; j++) {
if(nextScale != 0) {
int delta_scale;
ZOOOZOOO_SE(delta_scale);
nextScale = (lastScale + delta_scale + 256) % 256;
}
lastScale = (nextScale == 0) ? lastScale : nextScale;
}
}
}
}
}
int log2_max_frame_num_minus4;
ZOOOZOOO_UE(log2_max_frame_num_minus4);
int pic_order_cnt_type;
ZOOOZOOO_UE(pic_order_cnt_type);
if(pic_order_cnt_type == 0) {
int log2_max_pic_order_cnt_lsb_minus4;
ZOOOZOOO_UE(log2_max_pic_order_cnt_lsb_minus4);
} else if(pic_order_cnt_type == 1) {
int delta_pic_order_always_zero_flag;
ZOOOZOOO_BIT(delta_pic_order_always_zero_flag);
int offset_for_non_ref_pic;
ZOOOZOOO_SE(offset_for_non_ref_pic);
int offset_for_top_to_bottom_field;
ZOOOZOOO_SE(offset_for_top_to_bottom_field);
int num_ref_frames_in_pic_order_cnt_cycle;
ZOOOZOOO_UE(num_ref_frames_in_pic_order_cnt_cycle);
for(int i = 0; i < num_ref_frames_in_pic_order_cnt_cycle; i++) {
int offset_for_ref_frame_i;
ZOOOZOOO_SE(offset_for_ref_frame_i);
}
}
int max_num_ref_frames;
ZOOOZOOO_UE(max_num_ref_frames);
int gaps_in_frame_num_value_allowed_flag;
ZOOOZOOO_BIT(gaps_in_frame_num_value_allowed_flag);
int pic_width_in_mbs_minus1;
ZOOOZOOO_UE(pic_width_in_mbs_minus1);
int pic_height_in_map_units_minus1;
ZOOOZOOO_UE(pic_height_in_map_units_minus1);
int frame_mbs_only_flag;
ZOOOZOOO_BIT(frame_mbs_only_flag);
if(!frame_mbs_only_flag) {
int mb_adaptive_frame_field_flag;
ZOOOZOOO_BIT(mb_adaptive_frame_field_flag);
}
int direct_8x8_inference_flag;
ZOOOZOOO_BIT(direct_8x8_inference_flag);
int frame_cropping_flag;
ZOOOZOOO_BIT(frame_cropping_flag);
int frame_crop_left_offset = 0;
int frame_crop_right_offset = 0;
int frame_crop_top_offset = 0;
int frame_crop_bottom_offset = 0;
if(frame_cropping_flag) {
ZOOOZOOO_UE(frame_crop_left_offset);
ZOOOZOOO_UE(frame_crop_right_offset);
ZOOOZOOO_UE(frame_crop_top_offset);
ZOOOZOOO_UE(frame_crop_bottom_offset);
}
int vui_parameters_present_flag;
ZOOOZOOO_BIT(vui_parameters_present_flag);
*foundWidth = ((pic_width_in_mbs_minus1 + 1) * 16) - frame_crop_bottom_offset * 2 - frame_crop_top_offset * 2;
*foundHeight = (2 - frame_mbs_only_flag) * (pic_height_in_map_units_minus1 + 1) * 16 - frame_crop_right_offset * 2 - frame_crop_left_offset * 2;
}
static uint8_t *annexb_to_extradata(const uint8_t *src, const uint8_t *srcEnd, size_t *szRet, size_t *srcSzEnd) {
const uint8_t *sps = src;
while(*sps == 0) sps++;
assert(sps[0] == 1);
sps++;
size_t szSps = annexb_parse(sps, srcEnd);
const uint8_t *pps = sps + szSps;
while(*pps == 0) pps++;
assert(pps[0] == 1);
pps++;
size_t szPps = annexb_parse(pps, srcEnd);
uint8_t *ret = malloc(*szRet = (6 + 2 + szSps + 1 + 2 + szPps));
ret[0] = 1;
ret[1] = sps[1];
ret[2] = sps[2];
ret[3] = sps[3];
ret[4] = 0xFC | (NALLENSZ - 1);
ret[5] = 0xE0 | 1;
ret[6] = szSps >> 8;
ret[7] = szSps & 0xFF;
memcpy(&ret[8], sps, szSps);
ret[8 + szSps + 0] = 1;
ret[8 + szSps + 1] = szPps >> 8;
ret[8 + szSps + 2] = szPps & 0xFF;
memcpy(&ret[8 + szSps + 3], pps, szPps);
*srcSzEnd = pps + szPps - src;
return ret;
}
static uint8_t *annexb_to_avcc(const uint8_t *src, size_t szSrc, size_t *szRet) {
size_t cap = 4096, sz = 0;
uint8_t *ret = malloc(cap);
const uint8_t *srcEnd = src + szSrc;
while(src != srcEnd) {
assert(*src == 0);
while(*src == 0) {
src++;
}
assert(*src == 1);
src++;
size_t nalSize = annexb_parse(src, srcEnd);
size_t additionSz = NALLENSZ + nalSize;
if(sz + additionSz > cap) {
ret = realloc(ret, cap = (sz + additionSz));
}
*(uint32_t*) &ret[sz] = htonl(nalSize);
memcpy(&ret[sz + NALLENSZ], src, nalSize);
sz += additionSz;
src += nalSize;
}
*szRet = sz;
return ret;
}
struct Internal {
CHiPubNode pub;
FILE *fd;
EBMLWriter wr;
size_t videoTrack, audioTrack;
size_t currentClusterTimecode;
bool beforeFirstCluster;
bool codecPrivatesFound;
CHiBSFrames *audioBacklog;
CHiBSFrames *videoBacklog;
};
static size_t next_timestamp(struct Internal *this) {
if(this->videoBacklog && this->videoBacklog->count > 0 && (!this->audioBacklog || (this->audioBacklog->count > 0 && this->videoBacklog->data[0].timestamp <= this->audioBacklog->data[0].timestamp))) {
return this->videoBacklog->data[0].timestamp;
} else if(this->audioBacklog && this->audioBacklog->count > 0 && (!this->videoBacklog || (this->videoBacklog->count > 0 && this->audioBacklog->data[0].timestamp <= this->videoBacklog->data[0].timestamp))) {
return this->audioBacklog->data[0].timestamp;
}
abort();
}
static int muxmkv_perform(CHiPubNode *pubn) {
MTR_BEGIN("CHi", "muxmkv_perform");
struct Internal *this = (void*) pubn;
if(pubn->sinks[0].linked.to) {
CHiBSFrames *vp9bs = CHi_Crawl(&pubn->sinks[0])->data.bitstream;
if(vp9bs) {
this->videoBacklog = CHi_BS_Combine(this->videoBacklog, vp9bs);
}
}
if(pubn->sinks[1].linked.to) {
CHiBSFrames *opus = CHi_Crawl(&pubn->sinks[1])->data.bitstream;
if(opus) {
this->audioBacklog = CHi_BS_Combine(this->audioBacklog, opus);
}
}
if(this->codecPrivatesFound) {
while(
(!this->audioBacklog || this->audioBacklog->count > 0) &&
(!this->videoBacklog || this->videoBacklog->count > 0)) {
size_t nextTimestamp = next_timestamp(this);
bool shouldUpdateCluster = this->beforeFirstCluster || (this->videoBacklog && this->videoBacklog->count && (this->videoBacklog->data[0].flags & CUTIHI_BS_FLAG_KEY)) || (nextTimestamp - this->currentClusterTimecode > 15000);
if(shouldUpdateCluster) {
if(!this->beforeFirstCluster) {
ebml_writer_pop(&this->wr);
}
this->beforeFirstCluster = false;
// Cluster
ebml_writer_push(&this->wr, 0x1F43B675);
this->currentClusterTimecode = nextTimestamp;
// Timecode
ebml_writer_put(&this->wr, 0xE7, EBML_UNSIGNED_INTEGER, (EBMLPrimitive) {.uInt = this->currentClusterTimecode});
}
if(this->videoBacklog && this->videoBacklog->count && (!this->audioBacklog || (this->audioBacklog->count > 0 && this->videoBacklog->data[0].timestamp <= this->audioBacklog->data[0].timestamp))) {
CHiBSFrame *frame = &this->videoBacklog->data[0];
assert((frame->flags & CUTIHI_BS_SETUP_PACKET) == 0);
bool freeAvcc = false;
size_t avccSz = 0;
uint8_t *avcc = NULL;
if(CHi_Crawl(&pubn->sinks[0])->type == CUTIHI_VAL_H264BS) {
avcc = annexb_to_avcc(frame->ptr, frame->sz, &avccSz);
freeAvcc = true;
} else {
avcc = frame->ptr;
avccSz = frame->sz;
}
size_t simpleBlockSize = 4 + avccSz;
uint8_t *simpleBlock = malloc(simpleBlockSize);
simpleBlock[0] = 0x80 | this->videoTrack;
*(uint16_t*) &simpleBlock[1] = __builtin_bswap16(frame->timestamp - this->currentClusterTimecode);
simpleBlock[3] = (frame->flags & CUTIHI_BS_FLAG_KEY) ? 128 : 0;
memcpy(simpleBlock + 4, avcc, avccSz);
// SimpleBlock
ebml_writer_put(&this->wr, 0xA3, EBML_BINARY, (EBMLPrimitive) {.binary = {.length = simpleBlockSize, .ptr = simpleBlock}});
free(simpleBlock);
if(freeAvcc) {
free(avcc);
}
CHi_BS_Pop(this->videoBacklog, 1);
}
if(this->audioBacklog && this->audioBacklog->count && (!this->videoBacklog || (this->videoBacklog->count > 0 && this->audioBacklog->data[0].timestamp <= this->videoBacklog->data[0].timestamp))) {
CHiBSFrame *frame = &this->audioBacklog->data[0];
assert((frame->flags & CUTIHI_BS_SETUP_PACKET) == 0);
size_t simpleBlockSize = 4 + frame->sz;
uint8_t *simpleBlock = malloc(simpleBlockSize);
simpleBlock[0] = 0x80 | this->audioTrack;
*(uint16_t*) &simpleBlock[1] = __builtin_bswap16(frame->timestamp - this->currentClusterTimecode);
simpleBlock[3] = (frame->flags & CUTIHI_BS_FLAG_KEY) ? 128 : 0;
memcpy(simpleBlock + 4, frame->ptr, frame->sz);
// SimpleBlock
ebml_writer_put(&this->wr, 0xA3, EBML_BINARY, (EBMLPrimitive) {.binary = {.length = simpleBlockSize, .ptr = simpleBlock}});
free(simpleBlock);
CHi_BS_Pop(this->audioBacklog, 1);
}
}
} else if((!this->audioBacklog || this->audioBacklog->count) && (!this->videoBacklog || this->videoBacklog->count)) {
uint8_t randbuf[16];
// Segment
ebml_writer_push_ind(&this->wr, 0x18538067);
// Info
ebml_writer_push(&this->wr, 0x1549A966);
// SegmentUUID
getrandom(randbuf, sizeof(randbuf), 0);
ebml_writer_put(&this->wr, 0x73A4, EBML_BINARY, (EBMLPrimitive) {.binary = {.ptr = randbuf, .length = sizeof(randbuf)}});
// TimestampScale
ebml_writer_put(&this->wr, 0x2AD7B1, EBML_UNSIGNED_INTEGER, (EBMLPrimitive) {.uInt = 1000000});
// MuxingApp
ebml_writer_put(&this->wr, 0x4D80, EBML_STRING, (EBMLPrimitive) {.string = "cuticle"});
// WritingApp
ebml_writer_put(&this->wr, 0x5741, EBML_STRING, (EBMLPrimitive) {.string = "cuticle"});
ebml_writer_pop(&this->wr);
// Tracks
ebml_writer_push(&this->wr, 0x1654AE6B);
if(this->videoBacklog) {
// TrackEntry
ebml_writer_push(&this->wr, 0xAE);
// TrackNumber
ebml_writer_put(&this->wr, 0xD7, EBML_UNSIGNED_INTEGER, (EBMLPrimitive) {.uInt = this->videoTrack});
// TrackUID
getrandom(randbuf, sizeof(uint64_t), 0);
ebml_writer_put(&this->wr, 0x73C5, EBML_UNSIGNED_INTEGER, (EBMLPrimitive) {.uInt = *(uint64_t*) &randbuf[0]});
// TrackType
ebml_writer_put(&this->wr, 0x83, EBML_UNSIGNED_INTEGER, (EBMLPrimitive) {.uInt = 1});
// FlagLacing
ebml_writer_put(&this->wr, 0x9C, EBML_UNSIGNED_INTEGER, (EBMLPrimitive) {.uInt = 0});
CHiBSFrame *setup = &this->videoBacklog->data[0];
assert(setup->flags & CUTIHI_BS_SETUP_PACKET);
uint16_t width = 0, height = 0;
if(CHi_Crawl(&pubn->sinks[0])->type == CUTIHI_VAL_VP8BS) {
ebml_writer_put(&this->wr, 0x86, EBML_STRING, (EBMLPrimitive) {.string = "V_VP8"});
CHiVPxBSSettings *settings = setup->ptr;
width = settings->width;
height = settings->height;
setup = NULL;
CHi_BS_Pop(this->videoBacklog, 1);
} else if(CHi_Crawl(&pubn->sinks[0])->type == CUTIHI_VAL_VP9BS) {
ebml_writer_put(&this->wr, 0x86, EBML_STRING, (EBMLPrimitive) {.string = "V_VP9"});
CHiVPxBSSettings *settings = setup->ptr;
width = settings->width;
height = settings->height;
setup = NULL;
CHi_BS_Pop(this->videoBacklog, 1);
} else if(CHi_Crawl(&pubn->sinks[0])->type == CUTIHI_VAL_H264BS) {
ebml_writer_put(&this->wr, 0x86, EBML_STRING, (EBMLPrimitive) {.string = "V_MPEG4/ISO/AVC"});
size_t szRet, srcSzEnd;
uint8_t *extradata = annexb_to_extradata(setup->ptr, setup->ptr + setup->sz, &szRet, &srcSzEnd);
// CodecPrivate
ebml_writer_put(&this->wr, 0x63A2, EBML_BINARY, (EBMLPrimitive) {.binary = {.length = szRet, .ptr = extradata}});
get_dimensions_from_extradata(extradata, &width, &height);
free(extradata);
// Frame still contains keyframe because of legacy crap
memmove(setup->ptr, setup->ptr + srcSzEnd, setup->sz - srcSzEnd);
setup->sz -= srcSzEnd;
setup->flags &= ~CUTIHI_BS_SETUP_PACKET;
}
assert(width > 0 && height > 0);
// Video
ebml_writer_push(&this->wr, 0xE0);
// PixelWidth
ebml_writer_put(&this->wr, 0xB0, EBML_UNSIGNED_INTEGER, (EBMLPrimitive) {.uInt = width});
// PixelHeight
ebml_writer_put(&this->wr, 0xBA, EBML_UNSIGNED_INTEGER, (EBMLPrimitive) {.uInt = height});
ebml_writer_pop(&this->wr);
ebml_writer_pop(&this->wr);
}
if(this->audioBacklog) {
// TrackEntry
ebml_writer_push(&this->wr, 0xAE);
// TrackNumber
ebml_writer_put(&this->wr, 0xD7, EBML_UNSIGNED_INTEGER, (EBMLPrimitive) {.uInt = this->audioTrack});
// TrackUID
getrandom(randbuf, sizeof(uint64_t), 0);
ebml_writer_put(&this->wr, 0x73C5, EBML_UNSIGNED_INTEGER, (EBMLPrimitive) {.uInt = *(uint64_t*) &randbuf[0]});
// TrackType
ebml_writer_put(&this->wr, 0x83, EBML_UNSIGNED_INTEGER, (EBMLPrimitive) {.uInt = 2});
// FlagLacing
ebml_writer_put(&this->wr, 0x9C, EBML_UNSIGNED_INTEGER, (EBMLPrimitive) {.uInt = 0});
assert(this->audioBacklog->data[0].flags & CUTIHI_BS_SETUP_PACKET);
if(CHi_Crawl(&pubn->sinks[1])->type == CUTIHI_VAL_OPUSBS) {
ebml_writer_put(&this->wr, 0x86, EBML_STRING, (EBMLPrimitive) {.string = "A_OPUS"});
CHiBSFrame *opusHead = &this->audioBacklog->data[0];
// CodecPrivate
ebml_writer_put(&this->wr, 0x63A2, EBML_BINARY, (EBMLPrimitive) {.binary = {.length = opusHead->sz, .ptr = opusHead->ptr}});
} else if(CHi_Crawl(&pubn->sinks[1])->type == CUTIHI_VAL_AACBS) {
ebml_writer_put(&this->wr, 0x86, EBML_STRING, (EBMLPrimitive) {.string = "A_AAC/MPEG2/LC"});
}
// Audio
ebml_writer_push(&this->wr, 0xE1);
// SamplingFrequency
ebml_writer_put(&this->wr, 0xB0, EBML_FLOAT4, (EBMLPrimitive) {.flt4 = 48000});
// Channels
ebml_writer_put(&this->wr, 0x9F, EBML_UNSIGNED_INTEGER, (EBMLPrimitive) {.uInt = 1});
ebml_writer_pop(&this->wr);
ebml_writer_pop(&this->wr);
CHi_BS_Pop(this->audioBacklog, 1);
}
ebml_writer_pop(&this->wr);
this->codecPrivatesFound = true;
}
MTR_END("CHi", "muxmkv_perform");
return 1;
}
static void muxmkv_destroy(CHiPubNode *pubn) {
struct Internal *this = (void*) pubn;
CHi_BS_Free(this->audioBacklog);
this->audioBacklog = NULL;
CHi_BS_Free(this->videoBacklog);
this->videoBacklog = NULL;
free(this);
}
static size_t ebml_write(EBMLWriter *this, const void *data, size_t length) {
return fwrite(data, 1, length, this->ud);
}
static void *ebml_alloc(EBMLWriter *this, void *data, size_t length) {
return realloc(data, length);
}
static int muxmkv_start(CHiPubNode *pubn) {
struct Internal *this = (void*) pubn;
this->currentClusterTimecode = 0;
this->codecPrivatesFound = false;
this->beforeFirstCluster = true;
int trackNum = 1;
if(pubn->sinks[0].linked.to) {
this->videoBacklog = CHi_BS_Empty();
this->videoTrack = trackNum++;
}
if(pubn->sinks[1].linked.to) {
this->audioBacklog = CHi_BS_Empty();
this->audioTrack = trackNum++;
}
this->fd = fopen(CHi_Crawl(&pubn->sinks[CUTIHI_MUXWEBM_IN_FILENAME])->data.text, "wb");
if(!this->fd) {
CHi_AddError(pubn, "invalid file", CUTIHI_MUXWEBM_IN_FILENAME);
return 0;
}
ebml_writer_init(&this->wr);
this->wr.ud = this->fd;
this->wr.eventWrite = ebml_write;
this->wr.eventAlloc = ebml_alloc;
ebml_writer_push(&this->wr, 0x1A45DFA3);
ebml_writer_put(&this->wr, 0x4282, EBML_STRING, (EBMLPrimitive) {.string = "matroska"});
ebml_writer_put(&this->wr, 0x42F2, EBML_UNSIGNED_INTEGER, (EBMLPrimitive) {.uInt = 4});
ebml_writer_put(&this->wr, 0x42F3, EBML_UNSIGNED_INTEGER, (EBMLPrimitive) {.uInt = 8});
ebml_writer_put(&this->wr, 0x4287, EBML_UNSIGNED_INTEGER, (EBMLPrimitive) {.uInt = 2});
ebml_writer_put(&this->wr, 0x4285, EBML_UNSIGNED_INTEGER, (EBMLPrimitive) {.uInt = 2});
ebml_writer_pop(&this->wr);
return 1;
}
static int muxmkv_stop(CHiPubNode *pubn) {
struct Internal *this = (void*) pubn;
while(this->wr.currentDepth) {
ebml_writer_pop(&this->wr);
}
while(!ebml_writer_flush(&this->wr));
ebml_writer_free(&this->wr);
fclose(this->fd);
return 1;
}
CUTIVIS CHiPubNode *CHi_MuxMatroska() {
struct Internal *n = calloc(1, sizeof(*n));
n->pub.type = CUTIHI_T('CExp','Mkv ');
n->pub.Start = muxmkv_start;
n->pub.Perform = muxmkv_perform;
n->pub.Destroy = muxmkv_destroy;
n->pub.Stop = muxmkv_stop;
n->pub.sinks = calloc(sizeof(*n->pub.sinks), n->pub.sinkCount = 3);
n->pub.sourceCount = 0;
n->pub.sources = NULL;
return &n->pub;
}

655
hi/node.c
View File

@@ -2,8 +2,6 @@
#include<stdlib.h>
#include"img.h"
#include<sail/sail.h>
#include<sail-manip/sail-manip.h>
#include<assert.h>
#include<string.h>
#include<tmmintrin.h>
@@ -19,55 +17,10 @@
#include"linearity.h"
static size_t bisect(const void *key, const void *base, size_t nmemb, size_t size, ssize_t(*compar)(const void*, const void*)) {
size_t low = 0, high = nmemb;
#include"node_internal.h"
while(low < high) {
size_t middle = (low + high) / 2;
if(compar((const void*) ((uintptr_t) base + size * middle), key) < 0) {
low = middle + 1;
} else {
high = middle;
}
}
return low;
}
static ssize_t float_compar(const void *A, const void *B) {
float a = *(float*) A;
float b = *(float*) B;
return (a > b) - (a < b);
}
static int adjacencycmp(const void *a, const void *b) {
size_t v = (uintptr_t) ((CHiAdjacency*) a)[0] - (uintptr_t) ((CHiAdjacency*) b)[0];
return v ? v : (uintptr_t) ((CHiAdjacency*) a)[1] - (uintptr_t) ((CHiAdjacency*) b)[1];
}
static void adjacency_add(CHiPubNode *source, CHiPubNode *sink) {
CHiNodeGraph *ng = source->ng;
if(ng->adjacencyCount == ng->adjacencyCapacity) {
ng->adjacencies = realloc(ng->adjacencies, sizeof(CHiAdjacency) * (ng->adjacencyCapacity *= 2));
}
ng->adjacencies[ng->adjacencyCount][0] = source;
ng->adjacencies[ng->adjacencyCount][1] = sink;
ng->adjacencyCount++;
qsort(ng->adjacencies, ng->adjacencyCount, sizeof(CHiAdjacency), adjacencycmp);
}
static void adjacency_remove(CHiPubNode *source, CHiPubNode *sink) {
CHiNodeGraph *ng = source->ng;
CHiAdjacency *adj = bsearch(&(CHiAdjacency) {source, sink}, ng->adjacencies, ng->adjacencyCount, sizeof(CHiAdjacency), adjacencycmp);
if(adj) {
memmove(adj, adj + 1, sizeof(CHiAdjacency) * (ng->adjacencyCount - (adj - ng->adjacencies) - 1));
ng->adjacencyCount--;
}
}
#define STB_IMAGE_IMPLEMENTATION
#include<stb_image.h>
CUTIVIS CHiNodeGraph *CHi_NewNodeGraph() {
static int inited = 0;
@@ -125,9 +78,9 @@ CUTIVIS CHiNodeGraph *CHi_NodeGraphReset(CHiNodeGraph *ng) {
}
CUTIVIS CHiValue *CHi_Crawl(CHiValue *v) {
while(v->type == CUTIHI_VAL_LINKED || v->type == CUTIHI_VAL_KEYED) {
if(v->type == CUTIHI_VAL_LINKED) {
v = &v->data.linked.to->sources[v->data.linked.idx];
while(v->linked.to || v->type == CUTIHI_VAL_KEYED) {
if(v->linked.to) {
v = &v->linked.to->sources[v->linked.idx];
} else if(v->type == CUTIHI_VAL_KEYED) {
v = &v->data.keyed->current;
}
@@ -163,8 +116,8 @@ static int dfs_visit(size_t *resultCount, CHiPubNode ***result, CHiPubNode *n) {
n->_dfsmark = 1;
for(size_t s = 0; s < n->sinkCount; s++) {
if(n->sinks[s].type == CUTIHI_VAL_LINKED) {
if(!dfs_visit(resultCount, result, n->sinks[s].data.linked.to)) {
if(n->sinks[s].linked.to) {
if(!dfs_visit(resultCount, result, n->sinks[s].linked.to)) {
return 0;
}
}
@@ -209,28 +162,34 @@ CUTIVIS int CHi_ConfigureSink(CHiPubNode *n, size_t i, CHiValue v) {
return 1;
}
if(v.type == CUTIHI_VAL_LINKED && n == v.data.linked.to) return 0;
if(v.linked.to && n == v.linked.to) return 0;
CHiValue old = n->sinks[i];
if(old.type == CUTIHI_VAL_LINKED) {
adjacency_remove(old.data.linked.to, n);
if(old.linked.to) {
adjacency_remove(old.linked.to, n);
}
if(v.linked.to) {
// Overwrite only the link-related things to keep the old values in case the link is removed
n->sinks[i].linked = v.linked;
} else {
n->sinks[i] = v;
}
// Check if viable
n->sinks[i] = v;
if(n->ng && !topological_sort(n->ng)) {
n->sinks[i] = old;
if(old.type == CUTIHI_VAL_LINKED) {
adjacency_add(old.data.linked.to, n);
if(old.linked.to) {
adjacency_add(old.linked.to, n);
}
return 0;
}
if(v.type == CUTIHI_VAL_LINKED) {
adjacency_add(v.data.linked.to, n);
if(v.linked.to) {
adjacency_add(v.linked.to, n);
}
CHi_MakeDirty(n->ng, n);
@@ -351,22 +310,6 @@ CUTIVIS void CHi_SetDuration(CHiNodeGraph *ng, float d) {
ng->duration = d;
}
CUTIVIS int CHi_Hysteresis(CHiPubNode *root) {
if(root->ng->compilationStatus != CUTIHI_COMP_READY) return 0;
for(size_t s = 0; s < root->sinkCount; s++) {
if(root->sinks[s].type == CUTIHI_VAL_LINKED) {
CHi_Hysteresis(root->sinks[s].data.linked.to);
}
}
//if(!root->clean) {
root->Perform(root);
//}
return 1;
}
static bool error_changes(CHiPubNode *n) {
for(int e = 0; e < CUTIHI_MAX_ERRORS; e++) {
if(n->errors.active[e] != n->errors.activeLast[e]) {
@@ -384,9 +327,7 @@ static void save_errors(CHiPubNode *n) {
}
}
static void perform_step(CHiNodeGraph *ng) {
pthread_mutex_lock(&ng->mut);
CUTIVIS int CHi_Hysteresis(CHiNodeGraph *ng) {
for(size_t nIdx = 0; nIdx < ng->count; nIdx++) {
save_errors(ng->nodes[nIdx]);
}
@@ -394,14 +335,25 @@ static void perform_step(CHiNodeGraph *ng) {
for(size_t nIdx = 0; nIdx < ng->count; nIdx++) {
CHiPubNode *n = ng->nodes[nIdx];
n->Perform(n);
// The existence of n->Start implies the node has external side-effects and should not be executed
if(ng->compilationStatus == CUTIHI_COMP_RUNNING || n->Start == NULL) {
n->Perform(n);
if(error_changes(n)) {
if(ng->eventOnError)
ng->eventOnError(ng, n);
if(error_changes(n)) {
if(ng->eventOnError)
ng->eventOnError(ng, n);
}
}
}
return 1;
}
static void perform_step(CHiNodeGraph *ng) {
pthread_mutex_lock(&ng->mut);
CHi_Hysteresis(ng);
if(ng->eventOnFrameComplete) {
ng->eventOnFrameComplete(ng);
}
@@ -570,63 +522,56 @@ typedef struct {
CHiPubNode pubn;
char *cachePath;
CHiImage *cacheImg;
} ImageNode;
} ImageImpl;
static int image_perform(CHiPubNode *node) {
ImageNode *internal = (ImageNode*) node;
ImageImpl *impl = node->impl;
node->sources->type = CUTIHI_VAL_SAMPLE;
const char *fn = node->sinks[CUTIHI_IMAGE_IN_FILE].data.text;
if(fn && (!internal->cachePath || strcmp(internal->cachePath, fn))) {
if(fn && (!impl->cachePath || strcmp(impl->cachePath, fn))) {
if(node->sinks[CUTIHI_IMAGE_IN_FILE].type == CUTIHI_VAL_NONE) {
return 1;
}
if(node->sinks[CUTIHI_IMAGE_IN_FILE].type != CUTIHI_VAL_TEXT) {
node->errors.active[0] = true;
strncpy(node->errors.code[0], "invalid type", CUTIHI_ERR_SIZE);
node->errors.sink[0] = CUTIHI_IMAGE_IN_FILE;
CHi_AddError(node, "invalid type", 0);
return 1;
}
if(internal->cacheImg) {
CHi_Image_Free(internal->cacheImg);
internal->cacheImg = NULL;
if(impl->cacheImg) {
CHi_Image_Free(impl->cacheImg);
impl->cacheImg = NULL;
}
}
if(!internal->cacheImg) {
struct sail_image *simg;
if(sail_load_from_file(fn, &simg) != SAIL_OK) {
node->errors.active[0] = true;
strncpy(node->errors.code[0], "invalid file", CUTIHI_ERR_SIZE);
node->errors.sink[0] = CUTIHI_IMAGE_IN_FILE;
if(!impl->cacheImg) {
size_t w = 0, h = 0, n = 4;
float *data = stbi_loadf(fn, &w, &h, &n, 4);
if(!data) {
CHi_AddError(node, "invalid file", 0);
return 1;
}
struct sail_image *cimg;
sail_convert_image(simg, SAIL_PIXEL_FORMAT_BPP64_BGRA, &cimg);
sail_destroy_image(simg);
simg = NULL;
CHiImage *img = CHi_Image_New(2, 4, (cimg->bytes_per_line + 15) & ~15, cimg->width, cimg->height, NULL);
CHi_Restride(cimg->pixels, img->data16, cimg->bytes_per_line, img->stride, img->height);
internal->cacheImg = img;
CHiImage *img = CHi_Image_New(2, 4, (w * 8 + 15) & ~15, w, h, NULL);
impl->cacheImg = img;
for(size_t y = 0; y < img->height; y++) {
for(size_t x = 0; x < img->stride; x += 16) {
__m128i pixels = _mm_load_si128((__m128i*) ((uintptr_t) img->data16 + y * img->stride + x));
pixels = apply_gamma_epi16(pixels, _mm_set_ps(1.0f, 2.2f, 2.2f, 2.2f));
_mm_stream_si128((__m128i*) ((uintptr_t) img->data16 + y * img->stride + x), pixels);
for(size_t x = 0; x < img->width; x++) {
__m128 pixels = _mm_loadu_ps((__m128*) &data[w * y + x]);
pixels = apply_gamma_ps(pixels, _mm_set_ps(1.0f, 2.2f, 2.2f, 2.2f));
__m128i pixelsi = _mm_cvtps_epi32(_mm_mul_ps(_mm_min_ps(_mm_max_ps(pixels, _mm_set1_ps(0.0f)), _mm_set1_ps(1.0f)), _mm_set1_ps(65535.0f)));
pixelsi = _mm_shuffle_epi8(pixelsi, _mm_set_epi8(0, 1, 4, 5, 8, 9, 12, 13, -128, -128, -128, -128, -128, -128, -128, -128));
_mm_stream_si128((__m128i*) ((uintptr_t) img->data16 + y * img->stride + x), pixelsi);
}
}
sail_destroy_image(cimg);
free(impl->cachePath);
impl->cachePath = strdup(fn);
}
if(CHi_Node_Active(node)) {
node->sources->data.sample = internal->cacheImg;
node->sources->data.sample = impl->cacheImg;
} else {
node->sources->data.sample = NULL;
}
@@ -634,15 +579,10 @@ static int image_perform(CHiPubNode *node) {
return 1;
}
CUTIVIS CHiPubNode *CHi_Image() {
CHiPubNode *n = calloc(1, sizeof(ImageNode));
n->type = CUTIHI_T('CIma','ge ');
CHiPubNode *n = CHi_AllocNode(CUTIHI_T('CIma','ge '), 1, 1, sizeof(ImageImpl));
n->Start = n->Stop = NULL;
n->Perform = image_perform;
n->sinkCount = 1;
n->sinks = calloc(sizeof(*n->sinks), 1);
n->sourceCount = 1;
n->sources = calloc(sizeof(*n->sources), 1);
((ImageNode*) n)->cachePath = strdup("");
((ImageImpl*) n->impl)->cachePath = strdup("");
return n;
}
@@ -693,32 +633,42 @@ static int embed_perform(CHiPubNode *node) {
return 1;
}
CUTIVIS CHiPubNode *CHi_Embed() {
CHiPubNode *n = calloc(1, sizeof(*n));
n->type = CUTIHI_T('CEmb','ed ');
CHiPubNode *n = CHi_AllocNode(CUTIHI_T('CEmb','ed '), 1 + 3 * CUTIHI_EMBED_MAX_SMALLS, 1, 0);
n->Start = n->Stop = NULL;
n->Perform = embed_perform;
n->sinks = calloc(sizeof(*n->sinks), n->sinkCount = 1 + 3 * CUTIHI_EMBED_MAX_SMALLS);
n->sources = calloc(sizeof(*n->sources), n->sourceCount = 1);
return n;
}
struct ConstantSampleImpl {
size_t cacheW;
size_t cacheH;
float cacheCol[4];
};
static int constantsample_perform(CHiPubNode *node) {
struct ConstantSampleImpl *impl = node->impl;
node->sources[0].type = CUTIHI_VAL_SAMPLE;
if(node->sources->data.sample) CHi_Image_Free(node->sources->data.sample);
CHiValue *sink = CHi_Crawl(&node->sinks[0]);
CHiValue *color = CHi_Crawl(&node->sinks[0]);
CHiValue *sz = CHi_Crawl(&node->sinks[1]);
size_t w = sz->data.vec4[0] < 1 ? 1 : sz->data.vec4[0];
size_t h = sz->data.vec4[1] < 1 ? 1 : sz->data.vec4[1];
CHiImage *img = CHi_Image_New(2, 4, 8 * w, w, h, NULL);
impl->cacheW = w;
impl->cacheH = h;
memcpy(impl->cacheCol, color->data.vec4, sizeof(impl->cacheCol));
CHiImage *img = CHi_Image_New(2, 4, 8 * ((w + 1) & ~1), w, h, NULL);
if(CHi_Node_Active(node)) {
for(size_t i = 0; i < w * h; i++) {
img->data16[i * 4 + 0] = sink->data.vec4[2] * 65535;
img->data16[i * 4 + 1] = sink->data.vec4[1] * 65535;
img->data16[i * 4 + 2] = sink->data.vec4[0] * 65535;
img->data16[i * 4 + 3] = 65535;
for(size_t y = 0; y < h; y++) {
for(size_t x = 0; x < w; x++) {
img->data16[y * img->stride / 2 + x * 4 + 0] = color->data.vec4[2] * 65535;
img->data16[y * img->stride / 2 + x * 4 + 1] = color->data.vec4[1] * 65535;
img->data16[y * img->stride / 2 + x * 4 + 2] = color->data.vec4[0] * 65535;
img->data16[y * img->stride / 2 + x * 4 + 3] = 65535;
}
}
}
node->sources->data.sample = img;
@@ -726,18 +676,13 @@ static int constantsample_perform(CHiPubNode *node) {
return 1;
}
CUTIVIS CHiPubNode *CHi_ConstantSample() {
CHiPubNode *n = calloc(1, sizeof(*n));
n->type = CUTIHI_T('CCns','tCol');
CHiPubNode *n = CHi_AllocNode(CUTIHI_T('CCns','tCol'), 2, 1, sizeof(struct ConstantSampleImpl));
n->Start = n->Stop = NULL;
n->Perform = constantsample_perform;
n->sinkCount = 2;
n->sinks = calloc(sizeof(*n->sinks), n->sinkCount);
n->sourceCount = 1;
n->sources = calloc(sizeof(*n->sources), n->sourceCount);
n->sinks[0].type = CUTIHI_VAL_VEC4;
n->sinks[0].data.vec4[0] = 1280;
n->sinks[0].data.vec4[1] = 720;
n->sinks[1].type = CUTIHI_VAL_VEC4;
n->sinks[1].data.vec4[0] = 1280;
n->sinks[1].data.vec4[1] = 720;
return n;
}
@@ -764,9 +709,7 @@ static int modulate_perform(CHiPubNode *node) {
}
if(imgsrc->type != CUTIHI_VAL_SAMPLE) {
node->errors.active[0] = true;
strncpy(node->errors.code[0], "invalid type", CUTIHI_ERR_SIZE);
node->errors.sink[0] = 0;
CHi_AddError(node, "invalid type", 0);
return 1;
}
@@ -775,11 +718,15 @@ static int modulate_perform(CHiPubNode *node) {
if(!CHi_Node_Active(node)) {
node->sources->data.sample = NULL;
return;
return 1;
}
CHiImage *src = imgsrc->data.sample;
if(!src) {
return 1;
}
assert(src->stride % 16 == 0);
CHiImage *dst = CHi_Image_New(2, 4, src->stride, src->width, src->height, NULL);
@@ -899,14 +846,9 @@ static int modulate_perform(CHiPubNode *node) {
return 1;
}
CUTIVIS CHiPubNode *CHi_Modulate() {
CHiPubNode *n = calloc(1, sizeof(*n));
n->type = CUTIHI_T('CMod','ulat');
CHiPubNode *n = CHi_AllocNode(CUTIHI_T('CMod','ulat'), 4, 1, 0);
n->Start = n->Stop = NULL;
n->Perform = modulate_perform;
n->sinkCount = 4;
n->sinks = calloc(sizeof(*n->sinks), n->sinkCount);
n->sourceCount = 1;
n->sources = calloc(sizeof(*n->sources), n->sourceCount);
n->sinks[0].type = CUTIHI_VAL_VEC4;
n->sinks[0].data.vec4[0] = 1;
@@ -917,49 +859,6 @@ CUTIVIS CHiPubNode *CHi_Modulate() {
return n;
}
static void update_keyed_values(CHiNodeGraph *ng) {
for(size_t kfsIdx = 0; kfsIdx < ng->keyframesList.count; kfsIdx++) {
CHiKeyframes *kfs = ng->keyframesList.keyframes[kfsIdx];
kfs->current.type = kfs->type;
float now = ng->time;
size_t idx = bisect(&now, kfs->times, kfs->count, sizeof(now), float_compar);
if(idx == 0) {
kfs->current.data = kfs->values[idx];
if(kfs->current.type == CUTIHI_VAL_VEC4 && kfs->extrapolationMode == CUTIHI_EXTRAPOLATION_CONSTANT) {
kfs->current.data.vec4[0] += (now - kfs->times[0]) * kfs->extrapolationParameter[0];
kfs->current.data.vec4[1] += (now - kfs->times[0]) * kfs->extrapolationParameter[1];
kfs->current.data.vec4[2] += (now - kfs->times[0]) * kfs->extrapolationParameter[2];
kfs->current.data.vec4[3] += (now - kfs->times[0]) * kfs->extrapolationParameter[3];
}
} else if(idx == kfs->count) {
kfs->current.data = kfs->values[idx - 1];
if(kfs->current.type == CUTIHI_VAL_VEC4 && kfs->extrapolationMode == CUTIHI_EXTRAPOLATION_CONSTANT) {
kfs->current.data.vec4[0] += (now - kfs->times[kfs->count - 1]) * kfs->extrapolationParameter[0];
kfs->current.data.vec4[1] += (now - kfs->times[kfs->count - 1]) * kfs->extrapolationParameter[1];
kfs->current.data.vec4[2] += (now - kfs->times[kfs->count - 1]) * kfs->extrapolationParameter[2];
kfs->current.data.vec4[3] += (now - kfs->times[kfs->count - 1]) * kfs->extrapolationParameter[3];
}
} else {
if(kfs->type == CUTIHI_VAL_VEC4) {
float alpha = (now - kfs->times[idx - 1]) / (kfs->times[idx] - kfs->times[idx - 1]);
kfs->current.data.vec4[0] = kfs->values[idx - 1].vec4[0] + (kfs->values[idx].vec4[0] - kfs->values[idx - 1].vec4[0]) * alpha;
kfs->current.data.vec4[1] = kfs->values[idx - 1].vec4[1] + (kfs->values[idx].vec4[1] - kfs->values[idx - 1].vec4[1]) * alpha;
kfs->current.data.vec4[2] = kfs->values[idx - 1].vec4[2] + (kfs->values[idx].vec4[2] - kfs->values[idx - 1].vec4[2]) * alpha;
kfs->current.data.vec4[3] = kfs->values[idx - 1].vec4[3] + (kfs->values[idx].vec4[3] - kfs->values[idx - 1].vec4[3]) * alpha;
} else {
kfs->current.data = kfs->values[idx - 1];
}
}
}
}
static int time_perform(CHiPubNode *node) {
node->sources->type = CUTIHI_VAL_VEC4;
node->sources->data.vec4[0] = node->ng->time;
@@ -979,14 +878,9 @@ CUTIVIS float CHi_Time_GetDelta(CHiNodeGraph *ng) {
return ng->timedelta;
}
CUTIVIS CHiPubNode *CHi_Time() {
CHiPubNode *n = calloc(1, sizeof(*n));
n->type = CUTIHI_T('CTim','e ');
CHiPubNode *n = CHi_AllocNode(CUTIHI_T('CTim','e '), 0, 1, 0);
n->Start = n->Stop = NULL;
n->Perform = time_perform;
n->sinkCount = 0;
n->sinks = NULL;
n->sourceCount = 1;
n->sources = calloc(sizeof(*n->sources), 1);
return n;
}
@@ -1179,7 +1073,13 @@ CUTIVIS CHiPubNode *CHi_Preview() {
return n;
}
struct ChromaKeyImpl {
uint64_t cacheImg;
float cacheCol[4];
};
static int chromakey_perform(CHiPubNode *n) {
struct ChromaKeyImpl *impl = n->impl;
CHiValue *sampleV = CHi_Crawl(&n->sinks[0]);
CHiValue *colorV = CHi_Crawl(&n->sinks[1]);
@@ -1193,6 +1093,9 @@ static int chromakey_perform(CHiPubNode *n) {
CHi_Image_Free(n->sources[0].data.sample);
}
impl->cacheImg = src->uuid;
memcpy(impl->cacheCol, colorV->data.vec4, sizeof(impl->cacheCol));
n->sources[0].type = CUTIHI_VAL_SAMPLE;
CHiImage *dst = n->sources[0].data.sample = CHi_Image_New(2, 4, (src->width * src->bpc * src->channels + 15) & ~15, src->width, src->height, NULL);
@@ -1284,14 +1187,12 @@ static int chromakey_perform(CHiPubNode *n) {
return 1;
}
CUTIVIS CHiPubNode *CHi_ChromaKey() {
CHiPubNode *n = calloc(1, sizeof(*n));
n->type = CUTIHI_T('CChr','omaK');
CHiPubNode *n = CHi_AllocNode(CUTIHI_T('CChr','omaK'), 2, 1, sizeof(struct ChromaKeyImpl));
n->Start = n->Stop = NULL;
n->Perform = chromakey_perform;
n->sinks = calloc(sizeof(*n->sinks), n->sinkCount = 2);
n->sources = calloc(sizeof(*n->sources), n->sourceCount = 1);
n->sinks[1].type = CUTIHI_VAL_VEC4; // Default green
// Default green
n->sinks[1].type = CUTIHI_VAL_VEC4;
n->sinks[1].data.vec4[0] = 0;
n->sinks[1].data.vec4[1] = 1;
n->sinks[1].data.vec4[2] = 0;
@@ -1300,279 +1201,75 @@ CUTIVIS CHiPubNode *CHi_ChromaKey() {
return n;
}
static void save_chival(CHiNodeGraph *ng, CHiSaveWriter writer, CHiValType type, CHiValueRaw data, void *ud) {
if(type == CUTIHI_VAL_TEXT) {
size_t len = strlen(data.text);
writer(ud, &(uint32_t) {len}, sizeof(uint32_t));
writer(ud, data.text, len);
} else if(type == CUTIHI_VAL_VEC4) {
writer(ud, data.vec4, sizeof(data.vec4));
} else if(type == CUTIHI_VAL_LINKED) {
size_t index;
for(index = 0; index < ng->count; index++) {
if(ng->nodes[index] == data.linked.to) {
break;
}
}
assert(index < ng->count);
writer(ud, &(uint64_t) {index}, sizeof(uint64_t));
writer(ud, &(uint16_t) {data.linked.idx}, sizeof(uint16_t));
} else if(type == CUTIHI_VAL_KEYED) {
size_t index;
for(index = 0; index < ng->keyframesList.count; index++) {
if(ng->keyframesList.keyframes[index] == data.keyed) {
break;
}
}
assert(index < ng->count);
writer(ud, &(uint64_t) {index}, sizeof(uint64_t));
}
}
static void load_chival(CHiNodeGraph *ng, CHiLoadReader reader, CHiValType type, CHiValueRaw *data, void *ud) {
if(type == CUTIHI_VAL_TEXT) {
uint32_t len;
reader(ud, &len, sizeof(len));
data->text = malloc(len + 1);
reader(ud, data->text, len);
data->text[len] = 0;
} else if(type == CUTIHI_VAL_VEC4) {
reader(ud, data->vec4, sizeof(data->vec4));
} else if(type == CUTIHI_VAL_LINKED) {
uint64_t index;
reader(ud, &index, sizeof(index));
data->linked.to = ng->nodes[index];
uint16_t idx;
reader(ud, &idx, sizeof(idx));
data->linked.idx = idx;
} else if(type == CUTIHI_VAL_KEYED) {
uint64_t index;
reader(ud, &index, sizeof(index));
data->keyed = ng->keyframesList.keyframes[index];
}
}
CUTIVIS int CHi_NodeGraphSave(CHiNodeGraph *ng, CHiSaveWriter writer, void *ud) {
writer(ud, "\x71\x74\xCE\xA0", 4);
writer(ud, &(float) {ng->duration}, sizeof(float));
writer(ud, &(float) {ng->time}, sizeof(float));
writer(ud, &(uint64_t) {ng->keyframesList.count}, sizeof(uint64_t));
for(size_t i = 0; i < ng->keyframesList.count; i++) {
CHiKeyframes *kfs = ng->keyframesList.keyframes[i];
writer(ud, &(uint16_t) {kfs->type}, sizeof(uint16_t));
writer(ud, &(uint64_t) {kfs->count}, sizeof(uint64_t));
writer(ud, kfs->times, sizeof(*kfs->times) * kfs->count);
for(size_t k = 0; k < kfs->count; k++) {
save_chival(ng, writer, kfs->type, kfs->values[k], ud);
}
writer(ud, &(uint16_t) {kfs->extrapolationMode}, sizeof(uint16_t));
writer(ud, kfs->extrapolationParameter, sizeof(kfs->extrapolationParameter));
}
writer(ud, &(uint64_t) {ng->count}, sizeof(uint64_t));
for(size_t i = 0; i < ng->count; i++) {
CHiPubNode *node = ng->nodes[i];
writer(ud, &(uint64_t) {node->type}, sizeof(uint64_t));
}
for(size_t i = 0; i < ng->count; i++) {
CHiPubNode *node = ng->nodes[i];
if(node->Save) {
node->Save(node, ud, writer);
}
writer(ud, &(uint16_t) {node->sinkCount}, sizeof(uint16_t));
for(size_t sink = 0; sink < node->sinkCount; sink++) {
writer(ud, &(uint16_t) {node->sinks[sink].type}, sizeof(uint16_t));
save_chival(ng, writer, node->sinks[sink].type, node->sinks[sink].data, ud);
}
}
return 0;
}
CUTIVIS int CHi_NodeGraphLoad(CHiNodeGraph *ng, CHiLoadReader reader, void *ud) {
{
char magic[4];
reader(ud, magic, sizeof(magic));
if(memcmp(magic, "\x71\x74\xCE\xA0", 4)) {
return 1;
}
}
CHi_NodeGraphReset(ng);
reader(ud, &ng->duration, sizeof(float));
reader(ud, &ng->time, sizeof(float));
{
uint64_t count;
reader(ud, &count, sizeof(count));
ng->keyframesList.count = count;
}
for(size_t i = 0; i < ng->keyframesList.count; i++) {
CHiKeyframes *kfs = ng->keyframesList.keyframes[i] = calloc(1, sizeof(*kfs));
{
uint16_t type;
reader(ud, &type, sizeof(type));
kfs->type = type;
}
{
uint64_t count;
reader(ud, &count, sizeof(count));
kfs->count = count;
}
kfs->times = calloc(kfs->count, sizeof(*kfs->times));
reader(ud, kfs->times, kfs->count * sizeof(*kfs->times));
for(size_t k = 0; k < kfs->count; k++) {
load_chival(ng, reader, kfs->type, &kfs->values[k], ud);
}
{
uint16_t extrap;
reader(ud, &extrap, sizeof(extrap));
kfs->extrapolationMode = extrap;
}
reader(ud, kfs->extrapolationParameter, sizeof(kfs->extrapolationParameter));
}
{
uint64_t count;
reader(ud, &count, sizeof(count));
ng->count = count;
}
ng->capacity = ng->count < 8 ? 8 : ng->count;
ng->nodes = calloc(ng->capacity, sizeof(*ng->nodes));
for(size_t i = 0; i < ng->count; i++) {
uint64_t type;
reader(ud, &type, sizeof(type));
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();
}
n->ng = ng;
if(!n) {
CHi_NodeGraphReset(ng);
puts("Error: Unknown node type!");
return 1;
}
ng->nodes[i] = n;
}
for(size_t i = 0; i < ng->count; i++) {
CHiPubNode *n = ng->nodes[i];
{
uint16_t u16;
reader(ud, &u16, sizeof(u16));
n->sinkCount = u16;
}
n->sinks = calloc(n->sinkCount, sizeof(*n->sinks));
for(size_t s = 0; s < n->sinkCount; s++) {
{
uint16_t u16;
reader(ud, &u16, sizeof(u16));
n->sinks[s].type = u16;
}
load_chival(ng, reader, n->sinks[s].type, &n->sinks[s].data, ud);
if(n->sinks[s].type == CUTIHI_VAL_LINKED) {
adjacency_add(n->sinks[s].data.linked.to, n);
}
}
}
update_keyed_values(ng);
return 0;
}
CUTIVIS bool CHi_Node_Active(CHiPubNode *pubn) {
float now = CHi_Time_Get(pubn->ng);
return pubn->lifespan.start <= now && (pubn->lifespan.end == 0 || now < pubn->lifespan.end);
}
CUTIVIS void CHi_Node_Destroy(CHiPubNode *pubn) {
for(size_t ni = 0; ni < pubn->ng->count; ni++) {
for(size_t si = 0; si < pubn->ng->nodes[ni]->sinkCount; si++) {
if(pubn->ng->nodes[ni]->sinks[si].linked.to == pubn) {
adjacency_remove(pubn, pubn->ng->nodes[ni]);
pubn->ng->nodes[ni]->sinks[si].linked.to = NULL;
}
}
}
for(size_t si = 0; si < pubn->sinkCount; si++) {
if(pubn->sinks[si].linked.to) {
adjacency_remove(pubn->sinks[si].linked.to, pubn);
}
}
size_t ni;
for(ni = 0; ni < pubn->ng->count; ni++) {
if(pubn->ng->nodes[ni] == pubn) {
break;
}
}
assert(ni < pubn->ng->count);
if(pubn->Destroy) {
pubn->Destroy(pubn);
} else {
free(pubn);
}
memmove(&pubn->ng->nodes[ni], &pubn->ng->nodes[ni + 1], sizeof(*pubn->ng->nodes) * (pubn->ng->count - ni - 1));
pubn->ng->count--;
}
CUTIVIS void CHi_AddError(CHiPubNode *node, const char *err, size_t sink) {
for(size_t i = 0; i < CUTIHI_MAX_ERRORS; i++) {
if(!node->errors.active[i]) {
node->errors.active[i] = true;
strncpy(node->errors.code[i], "invalid type", CUTIHI_ERR_SIZE);
node->errors.sink[i] = sink;
break;
}
}
}
CUTIVIS CHiPubNode *CHi_AllocNode(uint64_t type, size_t sinkCount, size_t sourceCount, size_t implSize) {
CHiPubNode *n = calloc(1, sizeof(*n) + implSize);
if(!n) {
return NULL;
}
n->type = type;
n->sinks = calloc(n->sinkCount = sinkCount, sizeof(*n->sinks));
if(!n->sinks) {
free(n);
return NULL;
}
n->sources = calloc(n->sourceCount = sourceCount, sizeof(*n->sources));
if(!n->sources) {
free(n->sinks);
free(n);
return NULL;
}
return n;
}

View File

@@ -21,12 +21,18 @@ extern "C" {
#define CUTIHI_T(a, b) ((((uint64_t) b) << 32) | a
#endif
struct EBMLWriter;
struct CHiPubNode;
typedef size_t(*CHiSaveWriter)(void *ud, const void *data, size_t len);
typedef void(*CHiSaveCustomNodeData)(void *ud, struct EBMLWriter*, struct CHiPubNode*);
typedef size_t(*CHiLoadReader)(void *ud, void *data, size_t len);
typedef void(*CHiLoadCustomNodeData)(void *ud, uint64_t nodeIdx, uint64_t elId, const void *buf, size_t length);
typedef enum {
CUTIHI_VAL_NONE = 0,
CUTIHI_VAL_LINKED = 1,
// CUTIHI_VAL_LINKED = 1,
CUTIHI_VAL_KEYED = 2,
CUTIHI_VAL_SAMPLE = 3,
CUTIHI_VAL_TEXT = 4,
@@ -47,10 +53,6 @@ struct CHiPubNode;
struct CHiKeyframes;
typedef union {
struct {
struct CHiPubNode *to;
uint8_t idx;
} linked;
struct CHiImage *sample;
char *text;
float vec4[4];
@@ -61,6 +63,10 @@ typedef union {
typedef struct {
CHiValType type;
struct {
struct CHiPubNode *to;
uint8_t idx;
} linked;
CHiValueRaw data;
} CHiValue;
@@ -78,8 +84,6 @@ typedef struct CHiPubNode {
uint32_t flags;
#define CUTIHI_PERFORM_UPDATED 1
#define CUTIHI_PERFORM_NOTUPDATED 0
int (*Perform)(struct CHiPubNode *node);
int (*Start)(struct CHiPubNode *node);
int (*Stop)(struct CHiPubNode *node);
@@ -104,6 +108,8 @@ typedef struct CHiPubNode {
} lifespan;
char _dfsmark;
uint8_t impl[];
} CHiPubNode;
typedef enum {
@@ -167,8 +173,8 @@ typedef struct CHiNodeGraph {
CUTIVIS CHiNodeGraph *CHi_NewNodeGraph();
CUTIVIS CHiNodeGraph *CHi_NodeGraphReset(CHiNodeGraph *ng);
CUTIVIS int CHi_NodeGraphSave(CHiNodeGraph *ng, CHiSaveWriter writer, void *ud);
CUTIVIS int CHi_NodeGraphLoad(CHiNodeGraph *ng, CHiLoadReader reader, void *ud);
CUTIVIS int CHi_NodeGraphSave(CHiNodeGraph *ng, CHiSaveWriter, CHiSaveCustomNodeData, void *ud);
CUTIVIS int CHi_NodeGraphLoad(CHiNodeGraph *ng, CHiLoadReader, CHiLoadCustomNodeData, void *ud);
CUTIVIS void CHi_RegisterNode(CHiNodeGraph*, CHiPubNode*);
CUTIVIS void CHi_MakeDirty(CHiNodeGraph*, CHiPubNode*);
@@ -181,7 +187,7 @@ CUTIVIS size_t CHi_GetClosestKeyframe(CHiNodeGraph *ng, CHiKeyframes *kfs, float
CUTIVIS void CHi_SetExtrapolationMode(CHiNodeGraph *ng, CHiPubNode *n, size_t sinkIdx, CHiExtrapolationMode mode, float* params);
CUTIVIS void CHi_DeleteKeyframe(CHiNodeGraph *ng, CHiKeyframes *kfs, size_t idx);
CUTIVIS int CHi_Hysteresis(CHiPubNode*);
CUTIVIS int CHi_Hysteresis(CHiNodeGraph*);
CUTIVIS void CHi_SetDuration(CHiNodeGraph *ng, float);
CUTIVIS void CHi_BeginCompilation(CHiNodeGraph *ng);
@@ -240,10 +246,10 @@ CUTIVIS int CHi_MuxWebm_Stop(CHiPubNode*);
#define CUTIHI_WINDOW_OUT_SAMPLE 1
CUTIVIS CHiPubNode *CHi_Window();
CUTIVIS size_t CHi_Window_GetSourceCount();
CUTIVIS const char *CHi_Window_GetSourceName(size_t);
CUTIVIS uintptr_t CHi_Window_GetSourceData(size_t);
CUTIVIS size_t CHi_Window_GetNextSource(size_t);
CUTIVIS size_t CHi_Window_GetList(void **buf);
CUTIVIS const char *CHi_Window_GetName(void *buf, size_t i);
CUTIVIS size_t CHi_Window_GetHandle(void *buf, size_t i);
CUTIVIS void CHi_Window_FreeList(void *buf);
#define CUTIHI_MICROPHONE_IN_NAME 0
#define CUTIHI_MICROPHONE_OUT_AUDIO 1
@@ -275,12 +281,20 @@ CUTIVIS CHiPubNode *CHi_StreamRTMP();
CUTIVIS CHiPubNode *CHi_EncodeAAC();
CUTIVIS CHiPubNode *CHi_MuxMatroska();
CUTIVIS CHiValue *CHi_Crawl(CHiValue*);
//CUTIVIS void CHi_Save(CHiNodeGraph *ng);
CUTIVIS bool CHi_Node_Active(CHiPubNode*);
CUTIVIS void CHi_Node_Destroy(CHiPubNode*);
CUTIVIS void CHi_AddError(CHiPubNode*, const char *err, size_t sink);
CUTIVIS CHiPubNode *CHi_AllocNode(uint64_t type, size_t sinkCount, size_t sourceCount, size_t implSize);
#ifdef __cplusplus
}
#endif

96
hi/node_internal.h Normal file
View File

@@ -0,0 +1,96 @@
#pragma once
#include"node.h"
static inline size_t bisect(const void *key, const void *base, size_t nmemb, size_t size, ssize_t(*compar)(const void*, const void*)) {
size_t low = 0, high = nmemb;
while(low < high) {
size_t middle = (low + high) / 2;
if(compar((const void*) ((uintptr_t) base + size * middle), key) < 0) {
low = middle + 1;
} else {
high = middle;
}
}
return low;
}
static inline ssize_t float_compar(const void *A, const void *B) {
float a = *(float*) A;
float b = *(float*) B;
return (a > b) - (a < b);
}
static inline int adjacencycmp(const void *a, const void *b) {
size_t v = (uintptr_t) ((CHiAdjacency*) a)[0] - (uintptr_t) ((CHiAdjacency*) b)[0];
return v ? v : (uintptr_t) ((CHiAdjacency*) a)[1] - (uintptr_t) ((CHiAdjacency*) b)[1];
}
static inline void adjacency_add(CHiPubNode *source, CHiPubNode *sink) {
CHiNodeGraph *ng = source->ng;
if(ng->adjacencyCount == ng->adjacencyCapacity) {
ng->adjacencies = realloc(ng->adjacencies, sizeof(CHiAdjacency) * (ng->adjacencyCapacity *= 2));
}
ng->adjacencies[ng->adjacencyCount][0] = source;
ng->adjacencies[ng->adjacencyCount][1] = sink;
ng->adjacencyCount++;
qsort(ng->adjacencies, ng->adjacencyCount, sizeof(CHiAdjacency), adjacencycmp);
}
static inline void adjacency_remove(CHiPubNode *source, CHiPubNode *sink) {
CHiNodeGraph *ng = source->ng;
CHiAdjacency *adj = bsearch(&(CHiAdjacency) {source, sink}, ng->adjacencies, ng->adjacencyCount, sizeof(CHiAdjacency), adjacencycmp);
if(adj) {
memmove(adj, adj + 1, sizeof(CHiAdjacency) * (ng->adjacencyCount - (adj - ng->adjacencies) - 1));
ng->adjacencyCount--;
}
}
static inline void update_keyed_values(CHiNodeGraph *ng) {
for(size_t kfsIdx = 0; kfsIdx < ng->keyframesList.count; kfsIdx++) {
CHiKeyframes *kfs = ng->keyframesList.keyframes[kfsIdx];
kfs->current.type = kfs->type;
float now = ng->time;
size_t idx = bisect(&now, kfs->times, kfs->count, sizeof(now), float_compar);
if(idx == 0) {
kfs->current.data = kfs->values[idx];
if(kfs->current.type == CUTIHI_VAL_VEC4 && kfs->extrapolationMode == CUTIHI_EXTRAPOLATION_CONSTANT) {
kfs->current.data.vec4[0] += (now - kfs->times[0]) * kfs->extrapolationParameter[0];
kfs->current.data.vec4[1] += (now - kfs->times[0]) * kfs->extrapolationParameter[1];
kfs->current.data.vec4[2] += (now - kfs->times[0]) * kfs->extrapolationParameter[2];
kfs->current.data.vec4[3] += (now - kfs->times[0]) * kfs->extrapolationParameter[3];
}
} else if(idx == kfs->count) {
kfs->current.data = kfs->values[idx - 1];
if(kfs->current.type == CUTIHI_VAL_VEC4 && kfs->extrapolationMode == CUTIHI_EXTRAPOLATION_CONSTANT) {
kfs->current.data.vec4[0] += (now - kfs->times[kfs->count - 1]) * kfs->extrapolationParameter[0];
kfs->current.data.vec4[1] += (now - kfs->times[kfs->count - 1]) * kfs->extrapolationParameter[1];
kfs->current.data.vec4[2] += (now - kfs->times[kfs->count - 1]) * kfs->extrapolationParameter[2];
kfs->current.data.vec4[3] += (now - kfs->times[kfs->count - 1]) * kfs->extrapolationParameter[3];
}
} else {
if(kfs->type == CUTIHI_VAL_VEC4) {
float alpha = (now - kfs->times[idx - 1]) / (kfs->times[idx] - kfs->times[idx - 1]);
kfs->current.data.vec4[0] = kfs->values[idx - 1].vec4[0] + (kfs->values[idx].vec4[0] - kfs->values[idx - 1].vec4[0]) * alpha;
kfs->current.data.vec4[1] = kfs->values[idx - 1].vec4[1] + (kfs->values[idx].vec4[1] - kfs->values[idx - 1].vec4[1]) * alpha;
kfs->current.data.vec4[2] = kfs->values[idx - 1].vec4[2] + (kfs->values[idx].vec4[2] - kfs->values[idx - 1].vec4[2]) * alpha;
kfs->current.data.vec4[3] = kfs->values[idx - 1].vec4[3] + (kfs->values[idx].vec4[3] - kfs->values[idx - 1].vec4[3]) * alpha;
} else {
kfs->current.data = kfs->values[idx - 1];
}
}
}
}

View File

@@ -14,6 +14,8 @@ struct CHiEncodeOpusNode {
size_t timestamp;
bool firstFrame;
OpusEncoder *enc;
};
@@ -31,17 +33,41 @@ static int encodeopus_perform(CHiPubNode *pubn) {
n->pcmSamples += newpcm->width;
}
CHiBSFrames *frames = malloc(sizeof(*frames));
CHiBSFrames *frames = calloc(1, sizeof(*frames));
frames->count = 0;
if(!n->firstFrame) {
frames = CHi_BS_Grow(frames, 1);
struct OpusHead {
uint8_t magic[8];
uint8_t version;
uint8_t channelCount;
uint16_t preSkip;
uint32_t inputSampleRate;
uint16_t outputGain;
uint8_t mappingFamily;
} __attribute__((packed));
struct OpusHead *head = calloc(1, sizeof(*head));
*head = (struct OpusHead) {{'O', 'p', 'u', 's', 'H', 'e', 'a', 'd'}, 1, 1, 3840, 48000, 0, 0};
frames->data[0].timestamp = 0;
frames->data[0].flags = CUTIHI_BS_SETUP_PACKET;
frames->data[0].ptr = head;
frames->data[0].sz = sizeof(*head);
n->firstFrame = true;
}
size_t samp;
for(samp = 0; samp + 960 <= n->pcmSamples; samp += 960) {
frames = realloc(frames, sizeof(*frames) + sizeof(*frames->data) * (frames->count + 1));
frames->data[frames->count].timestamp = n->timestamp;
frames->data[frames->count].ptr = malloc(1276);
frames->data[frames->count].sz = opus_encode(n->enc, &n->pcmbuf[samp], 960, frames->data[frames->count].ptr, 1276);
if(frames->data[frames->count].sz < 0) puts("OPUS ERR");
frames->count++;
frames = CHi_BS_Grow(frames, 1);
frames->data[frames->count - 1].timestamp = n->timestamp;
frames->data[frames->count - 1].ptr = malloc(1276);
frames->data[frames->count - 1].sz = opus_encode(n->enc, &n->pcmbuf[samp], 960, frames->data[frames->count - 1].ptr, 1276);
if(frames->data[frames->count - 1].sz < 0) puts("OPUS ERR");
n->timestamp += 20;
}

149
hi/relay_win.c Normal file
View File

@@ -0,0 +1,149 @@
#include"relay.h"
#include"img.h"
#include<tmmintrin.h>
#include<stdio.h>
#include<pthread.h>
#include<stdatomic.h>
#include<math.h>
#include<windows.h>
#include<winuser.h>
#include<pthread.h>
static int scale_perform(CHiPubNode *n) {
float *scales = CHi_Crawl(&n->sinks[0])->data.vec4;
CHiImage *img = CHi_Crawl(&n->sinks[1])->data.sample;
if(n->sources[0].data.sample) {
CHi_Image_Free(n->sources[0].data.sample);
}
CHiImage *ret = n->sources[0].data.sample = CHi_Image_New(img->bpc, img->channels, img->stride, img->width, img->height, NULL);
__m128i iscales = _mm_set_epi16(
scales[3] * 65535, scales[0] * 65535, scales[1] * 65535, scales[2] * 65535,
scales[3] * 65535, scales[0] * 65535, scales[1] * 65535, scales[2] * 65535
);
for(size_t y = 0; y < img->height; y++) {
for(size_t x = 0; x < img->width; x += 16) {
__m128i pixels8 = _mm_loadu_si128((__m128i*) ((uintptr_t) img->data16 + y * img->stride + x));
__m128i mulled = _mm_mulhi_epu16(pixels8, iscales);
_mm_storeu_si128((__m128i*) ((uintptr_t) ret->data16 + y * img->stride + x), mulled);
}
}
return 1;
}
CUTIVIS CHiPubNode *CHi_ComponentScale() {
CHiPubNode *n = calloc(1, sizeof(*n));
n->type = CUTIHI_T('CCmp','nScl');
n->Start = n->Stop = NULL;
n->Perform = scale_perform;
n->sinkCount = 2;
n->sinks = calloc(sizeof(*n->sinks), n->sinkCount);
n->sourceCount = 1;
n->sources = calloc(sizeof(*n->sources), n->sourceCount);
return n;
}
typedef struct {
CHiPubNode pub;
pthread_t thrd;
char key[64];
atomic_bool on;
atomic_bool active;
} CHiKeyhookNode;
static _Thread_local CHiKeyhookNode *lookwhatyoumademedo;
static LRESULT CALLBACK keyhook_handler(int nCode, WPARAM wParam, LPARAM lParam) {
bool eatKeystroke = false;
CHiKeyhookNode *n = lookwhatyoumademedo;
if(nCode == HC_ACTION) {
switch(wParam) {
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
case WM_KEYUP:
case WM_SYSKEYUP: {
PKBDLLHOOKSTRUCT p = (PKBDLLHOOKSTRUCT) lParam;
bool press = wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN;
char keyname[64];
GetKeyNameTextA(p->vkCode, keyname, sizeof(keyname));
if(!strcmp(keyname, n->key)) {
n->on = press;
}
break;
}
}
}
return eatKeystroke ? true : CallNextHookEx(NULL, nCode, wParam, lParam);
}
static void *keyhook_thread(void *ud) {
CHiKeyhookNode *n = ud;
lookwhatyoumademedo = n;
HHOOK hhkLowLevelKybd = SetWindowsHookEx(WH_KEYBOARD_LL, keyhook_handler, 0, 0);
MSG msg;
while(n->active && !GetMessage(&msg, NULL, NULL, NULL)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
UnhookWindowsHookEx(hhkLowLevelKybd);
return NULL;
}
static int keyhook_perform(CHiPubNode *n) {
CHiKeyhookNode *me = (CHiKeyhookNode*) n;
strncpy(me->key, CHi_Crawl(&n->sinks[0])->data.text, 63);
me->key[63] = '\0';
n->sources[0].type = CUTIHI_VAL_VEC4;
if(n->ng->compilationStatus == CUTIHI_COMP_READY || n->sinks[1].data.vec4[0] == 0) {
n->sources[0].data.vec4[0] = ((CHiKeyhookNode*) n)->on;
} else if(((CHiKeyhookNode*) n)->on) {
n->sources[0].data.vec4[0] = fminf(1, n->sources[0].data.vec4[0] + CHi_Time_GetDelta(n->ng) * n->sinks[1].data.vec4[0]);
} else {
n->sources[0].data.vec4[0] = fmaxf(0, n->sources[0].data.vec4[0] - CHi_Time_GetDelta(n->ng) * n->sinks[1].data.vec4[0]);
}
return 1;
}
static void keyhook_destroy(CHiPubNode *pubn) {
CHiKeyhookNode *n = (void*) pubn;
n->active = false;
pthread_join(n->thrd, NULL);
free(n);
}
CUTIVIS CHiPubNode *CHi_Keyhook() {
CHiKeyhookNode *n = calloc(1, sizeof(*n));
n->pub.type = CUTIHI_T('CKey','hook');
n->pub.Start = n->pub.Stop = NULL;
n->pub.Perform = keyhook_perform;
n->pub.Destroy = keyhook_destroy;
n->pub.sinkCount = 2;
n->pub.sinks = calloc(sizeof(*n->pub.sinks), n->pub.sinkCount);
n->pub.sourceCount = 1;
n->pub.sources = calloc(sizeof(*n->pub.sources), n->pub.sourceCount);
n->on = 0;
n->key[0] = '\n';
n->active = true;
pthread_create(&n->thrd, NULL, keyhook_thread, n);
return &n->pub;
}

View File

@@ -9,8 +9,12 @@
#include<librtmp/rtmp.h>
#include<librtmp/log.h>
#ifdef _WIN32
#include<winsock2.h>
#else
#include<arpa/inet.h>
#include<sys/select.h>
#endif
#include"img.h"

382
hi/serialize.c Normal file
View File

@@ -0,0 +1,382 @@
#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;
}

View File

@@ -32,6 +32,8 @@ typedef struct CHiEncodeVP9Node {
uint8_t *outY, *outU, *outV;
uint16_t strideY, strideU, strideV;
bool firstFrame;
vpx_codec_iface_t *iface;
} CHiEncodeVP9Node;
@@ -40,7 +42,7 @@ static int encodevpx_perform(CHiPubNode *pub) {
MTR_BEGIN("CHi", "encodevp9_perform");
pub->sources[0].type = CUTIHI_VAL_VP9BS;
pub->sources[0].type = pub->type == CUTIHI_T('CEnc','GVP8') ? CUTIHI_VAL_VP8BS : CUTIHI_VAL_VP9BS;
pub->sources[0].data.bitstream = NULL;
if(node->state == WAITING) return 1;
@@ -70,20 +72,33 @@ static int encodevpx_perform(CHiPubNode *pub) {
vpx_codec_encode(&node->codec, &vpxraw, CHi_Time_Get(pub->ng) * 1000.f, 1, 0, VPX_DL_REALTIME);
CHiBSFrames *ret = malloc(sizeof(CHiBSFrames));
ret->count = 0;
CHiBSFrames *ret = calloc(1, sizeof(CHiBSFrames));
if(!node->firstFrame) {
ret = CHi_BS_Grow(ret, 1);
CHiVPxBSSettings *s = calloc(1, sizeof(*s));
s->width = vpxraw.w;
s->height = vpxraw.h;
ret->data[0].timestamp = 0;
ret->data[0].sz = sizeof(CHiVPxBSSettings);
ret->data[0].flags = CUTIHI_BS_SETUP_PACKET;
ret->data[0].ptr = s;
node->firstFrame = true;
}
vpx_codec_iter_t iter = NULL;
const vpx_codec_cx_pkt_t *pkt;
while((pkt = vpx_codec_get_cx_data(&node->codec, &iter)) != NULL) {
if(pkt->kind == VPX_CODEC_CX_FRAME_PKT) {
ret = (CHiBSFrames*) realloc(ret, sizeof(CHiBSFrames) + sizeof(CHiBSFrame) * (ret->count + 1));
ret->data[ret->count].timestamp = pkt->data.frame.pts;
ret->data[ret->count].sz = pkt->data.frame.sz;
ret->data[ret->count].flags = pkt->data.frame.flags & VPX_FRAME_IS_KEY;
ret->data[ret->count].ptr = malloc(ret->data[ret->count].sz);
memcpy(ret->data[ret->count].ptr, pkt->data.frame.buf, ret->data[ret->count].sz);
ret->count++;
ret = CHi_BS_Grow(ret, 1);
ret->data[ret->count - 1].timestamp = pkt->data.frame.pts;
ret->data[ret->count - 1].sz = pkt->data.frame.sz;
ret->data[ret->count - 1].flags = pkt->data.frame.flags & VPX_FRAME_IS_KEY;
ret->data[ret->count - 1].ptr = malloc(ret->data[ret->count - 1].sz);
memcpy(ret->data[ret->count - 1].ptr, pkt->data.frame.buf, ret->data[ret->count - 1].sz);
}
}
@@ -110,24 +125,23 @@ static int encodevpx_start(CHiPubNode *pubn) {
vpx_codec_enc_config_default(node->iface, &node->cfg, 0);
node->cfg.g_w = firstFrame->width;
node->cfg.g_h = firstFrame->height;
node->cfg.g_profile = 2;
node->cfg.g_profile = 0;
node->cfg.g_timebase.num = 1;
node->cfg.g_timebase.den = 30;
node->cfg.g_lag_in_frames = 0;
node->cfg.g_threads = 4;
node->cfg.kf_mode = VPX_KF_AUTO;
node->cfg.kf_max_dist = 300;
node->cfg.rc_end_usage = VPX_CBR;
node->cfg.rc_target_bitrate = 3000;
node->cfg.rc_min_quantizer = 24;
node->cfg.rc_end_usage = VPX_VBR;
node->cfg.rc_target_bitrate = 2000;
node->cfg.rc_min_quantizer = 0;
node->cfg.rc_max_quantizer = 32;
node->cfg.rc_undershoot_pct = 95;
vpx_codec_enc_init(&node->codec, node->iface, &node->cfg, 0);
vpx_codec_control(&node->codec, VP8E_SET_CPUUSED, 9);
//vpx_codec_control(&node->codec, VP9E_SET_SVC, 1);
vpx_codec_control(&node->codec, VP8E_SET_CPUUSED, 4);
vpx_codec_control(&node->codec, VP9E_SET_ROW_MT, 1);
//vpx_codec_control(&node->codec, VP9E_SET_TILE_COLUMNS, 3);
vpx_codec_control(&node->codec, VP9E_SET_TILE_COLUMNS, 2);
vpx_codec_control(&node->codec, VP9E_SET_TILE_ROWS, 1);
vpx_codec_control(&node->codec, VP9E_SET_NOISE_SENSITIVITY, 0);
vpx_codec_control(&node->codec, VP9E_SET_TUNE_CONTENT, VP9E_CONTENT_SCREEN);

View File

@@ -1,130 +1,20 @@
#include"node.h"
#include<sys/ioctl.h>
#include<sys/time.h>
#include<sys/mman.h>
#include<stdlib.h>
#include<stdio.h>
#include<fcntl.h>
#include<string.h>
#include<time.h>
#include<linux/videodev2.h>
#include<libv4l2.h>
#include<errno.h>
#include"img.h"
#include<assert.h>
#include<smmintrin.h>
static int camId = -1;
static struct Buf {
size_t length;
uint8_t *ptr;
} bufs[2];
struct v4l2_format fmt;
static void xioctl(int fh, int request, void *arg) {
int r;
do {
r = v4l2_ioctl(fh, request, arg);
} while(r == -1 && ((errno == EINTR) || (errno == EAGAIN)));
if(r == -1) {
fprintf(stderr, "error %d, %s\\n", errno, strerror(errno));
exit(EXIT_FAILURE);
}
}
static int camera_perform(CHiPubNode *pubn) {
pubn->sources[0].type = CUTIHI_VAL_SAMPLE;
fd_set fds;
FD_ZERO(&fds);
FD_SET(camId, &fds);
int r = select(camId + 1, &fds, NULL, NULL, &(struct timeval) {.tv_sec = 0, .tv_usec = 100});
if(r == -1) {
return 1;
}
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
xioctl(camId, VIDIOC_DQBUF, &buf);
CHiImage *ret;
if(pubn->sources[0].data.sample) {
ret = pubn->sources[0].data.sample;
} else {
ret = CHi_Image_New(2, 4, 8 * ((fmt.fmt.pix.width + 15) & ~15), fmt.fmt.pix.width, fmt.fmt.pix.height, NULL);
pubn->sources[0].data.sample = ret;
}
/* 24-to-32 TODO: make faster by processing 48-to-64 bytes in one iteration (like bgra32torgb24 does) */
for(size_t y = 0; y < ret->height; y++) {
for(size_t x = 0; x < ret->width; x += 2) {
__m128i asdf = _mm_loadu_si128((__m128i*) &bufs[buf.index].ptr[(y * ret->width + x) * 3]);
asdf = _mm_shuffle_epi8(asdf, _mm_set_epi8(-128, -128, 5, -128, 4, -128, 3, -128, -128, -128, 2, -128, 1, -128, 0, -128));
asdf = _mm_or_si128(asdf, _mm_set_epi32(0xFFFF0000, 0, 0xFFFF0000, 0));
_mm_stream_si128((__m128i*) ((uintptr_t) ret->data16 + y * ret->stride + x * 8), asdf);
}
}
xioctl(camId, VIDIOC_QBUF, &buf);
pubn->sources[0].type = CUTIHI_VAL_NONE;
return 1;
}
CUTIVIS CHiPubNode *CHi_Camera() {
if(camId == -1) {
camId = v4l2_open("/dev/video0", O_RDWR | O_NONBLOCK, 0);
memset(&fmt, 0, sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 640;
fmt.fmt.pix.height = 480;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_BGR24;
fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
xioctl(camId, VIDIOC_S_FMT, &fmt);
assert(fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_BGR24);
struct v4l2_requestbuffers req;
memset(&req, 0, sizeof(req));
req.count = 2;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
xioctl(camId, VIDIOC_REQBUFS, &req);
for(int i = 0; i < 2; i++) {
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
xioctl(camId, VIDIOC_QUERYBUF, &buf);
bufs[i].length = buf.length;
bufs[i].ptr = v4l2_mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, camId, buf.m.offset);
assert(MAP_FAILED != bufs[i].ptr);
}
for(int i = 0; i < 2; i++) {
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
xioctl(camId, VIDIOC_QBUF, &buf);
}
xioctl(camId, VIDIOC_STREAMON, &(enum v4l2_buf_type) {V4L2_BUF_TYPE_VIDEO_CAPTURE});
}
CHiPubNode *pubn = calloc(1, sizeof(*pubn));
pubn->type = CUTIHI_T('CWeb','Cam ');
pubn->Start = pubn->Stop = NULL;

136
hi/webcam_v4l2.c Normal file
View File

@@ -0,0 +1,136 @@
#include"node.h"
#include<sys/ioctl.h>
#include<sys/time.h>
#include<sys/mman.h>
#include<stdlib.h>
#include<stdio.h>
#include<fcntl.h>
#include<string.h>
#include<time.h>
#include<linux/videodev2.h>
#include<libv4l2.h>
#include<errno.h>
#include"img.h"
#include<assert.h>
#include<smmintrin.h>
static int camId = -1;
static struct Buf {
size_t length;
uint8_t *ptr;
} bufs[2];
struct v4l2_format fmt;
static void xioctl(int fh, int request, void *arg) {
int r;
do {
r = v4l2_ioctl(fh, request, arg);
} while(r == -1 && ((errno == EINTR) || (errno == EAGAIN)));
if(r == -1) {
fprintf(stderr, "error %d, %s\\n", errno, strerror(errno));
exit(EXIT_FAILURE);
}
}
static int camera_perform(CHiPubNode *pubn) {
pubn->sources[0].type = CUTIHI_VAL_SAMPLE;
fd_set fds;
FD_ZERO(&fds);
FD_SET(camId, &fds);
int r = select(camId + 1, &fds, NULL, NULL, &(struct timeval) {.tv_sec = 0, .tv_usec = 100});
if(r == -1) {
return 1;
}
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
xioctl(camId, VIDIOC_DQBUF, &buf);
CHiImage *ret;
if(pubn->sources[0].data.sample) {
ret = pubn->sources[0].data.sample;
} else {
ret = CHi_Image_New(2, 4, 8 * ((fmt.fmt.pix.width + 15) & ~15), fmt.fmt.pix.width, fmt.fmt.pix.height, NULL);
pubn->sources[0].data.sample = ret;
}
/* 24-to-32 TODO: make faster by processing 48-to-64 bytes in one iteration (like bgra32torgb24 does) */
for(size_t y = 0; y < ret->height; y++) {
for(size_t x = 0; x < ret->width; x += 2) {
__m128i asdf = _mm_loadu_si128((__m128i*) &bufs[buf.index].ptr[(y * ret->width + x) * 3]);
asdf = _mm_shuffle_epi8(asdf, _mm_set_epi8(-128, -128, 5, -128, 4, -128, 3, -128, -128, -128, 2, -128, 1, -128, 0, -128));
asdf = _mm_or_si128(asdf, _mm_set_epi32(0xFFFF0000, 0, 0xFFFF0000, 0));
_mm_stream_si128((__m128i*) ((uintptr_t) ret->data16 + y * ret->stride + x * 8), asdf);
}
}
xioctl(camId, VIDIOC_QBUF, &buf);
return 1;
}
CUTIVIS CHiPubNode *CHi_Camera() {
if(camId == -1) {
camId = v4l2_open("/dev/video0", O_RDWR | O_NONBLOCK, 0);
memset(&fmt, 0, sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 640;
fmt.fmt.pix.height = 480;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_BGR24;
fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
xioctl(camId, VIDIOC_S_FMT, &fmt);
assert(fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_BGR24);
struct v4l2_requestbuffers req;
memset(&req, 0, sizeof(req));
req.count = 2;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
xioctl(camId, VIDIOC_REQBUFS, &req);
for(int i = 0; i < 2; i++) {
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
xioctl(camId, VIDIOC_QUERYBUF, &buf);
bufs[i].length = buf.length;
bufs[i].ptr = v4l2_mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, camId, buf.m.offset);
assert(MAP_FAILED != bufs[i].ptr);
}
for(int i = 0; i < 2; i++) {
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
xioctl(camId, VIDIOC_QBUF, &buf);
}
xioctl(camId, VIDIOC_STREAMON, &(enum v4l2_buf_type) {V4L2_BUF_TYPE_VIDEO_CAPTURE});
}
CHiPubNode *pubn = calloc(1, sizeof(*pubn));
pubn->type = CUTIHI_T('CWeb','Cam ');
pubn->Start = pubn->Stop = NULL;
pubn->Perform = camera_perform;
pubn->sinks = calloc(sizeof(*pubn->sinks), pubn->sinkCount = 0);
pubn->sources = calloc(sizeof(*pubn->sources), pubn->sourceCount = 1);
pubn->ng = NULL;
return pubn;
}

View File

@@ -43,7 +43,7 @@ static int muxwebm_perform(CHiPubNode *pubn) {
CHiMuxWebmNode *alln = (CHiMuxWebmNode*) pubn;
if(pubn->sinks[1].data.linked.to) {
if(pubn->sinks[1].linked.to) {
CHiBSFrames *opus = CHi_Crawl(&pubn->sinks[1])->data.bitstream;
for(size_t i = 0; i < opus->count; i++) {
alln->audioBacklog.push(opus->data[i]);
@@ -57,7 +57,7 @@ static int muxwebm_perform(CHiPubNode *pubn) {
}
}
while(pubn->sinks[1].data.linked.to && alln->audioBacklog.size() > 0 && alln->videoBacklog.size() > 0 && alln->audioBacklog.front().timestamp <= alln->videoBacklog.front().timestamp) {
while(pubn->sinks[1].linked.to && alln->audioBacklog.size() > 0 && alln->videoBacklog.size() > 0 && alln->audioBacklog.front().timestamp <= alln->videoBacklog.front().timestamp) {
Frame frame;
frame.Init((const uint8_t*) alln->audioBacklog.front().ptr, alln->audioBacklog.front().sz);
frame.set_track_number(alln->audioTrack);
@@ -68,7 +68,7 @@ static int muxwebm_perform(CHiPubNode *pubn) {
alln->audioBacklog.pop();
}
if(pubn->sinks[1].data.linked.to == NULL || (alln->audioBacklog.size() > 0 && alln->videoBacklog.size() > 0 && alln->audioBacklog.front().timestamp >= alln->videoBacklog.front().timestamp)) {
if(pubn->sinks[1].linked.to == NULL || (alln->audioBacklog.size() > 0 && alln->videoBacklog.size() > 0 && alln->audioBacklog.front().timestamp >= alln->videoBacklog.front().timestamp)) {
Frame frame;
if(!frame.Init((const uint8_t*) alln->videoBacklog.front().ptr, alln->videoBacklog.front().sz)) puts("INIT FAIL");
frame.set_track_number(alln->videoTrack);
@@ -122,7 +122,7 @@ CUTIVIS int CHi_MuxWebm_Start(CHiPubNode *pubn) {
alln->seg.GetSegmentInfo()->set_writing_app("Cuticle");
/* Hack into first frame to get resolution */
CHiPubNode *evp9 = pubn->sinks[0].data.linked.to;
CHiPubNode *evp9 = pubn->sinks[0].linked.to;
CHiImage *firstFrame = (CHiImage*) CHi_Crawl(&evp9->sinks[0])->data.sample;
alln->videoTrack = alln->seg.AddVideoTrack(firstFrame->width, firstFrame->height, 0);
@@ -137,7 +137,7 @@ CUTIVIS int CHi_MuxWebm_Start(CHiPubNode *pubn) {
track->SetColour(colourspace);
alln->seg.CuesTrack(alln->videoTrack);
if(pubn->sinks[1].data.linked.to) {
if(pubn->sinks[1].linked.to) {
struct __attribute__((packed)) {
uint32_t magic1;
uint32_t magic2;

140
hi/window_scl.cpp Normal file
View File

@@ -0,0 +1,140 @@
#include"node.h"
#include<stdlib.h>
#include<ScreenCapture.h>
#include<mutex>
#include<atomic>
#include<tmmintrin.h>
#include<smmintrin.h>
#include<time.h>
#include<string.h>
#include"img.h"
#include"linearity.h"
#include"minitrace.h"
typedef struct {
CHiPubNode pub;
char* lastWindowString;
std::shared_ptr<SL::Screen_Capture::IScreenCaptureManager> config;
std::mutex mut;
std::vector<CHiImage*> images;
std::atomic<bool> ignoreAlpha;
} CHiWindowNode;
static int window_perform(CHiPubNode *n) {
CHiWindowNode *w = (CHiWindowNode*) n;
MTR_BEGIN("CHi", "window_perform");
CHiValue *ignoreAlphaVal = CHi_Crawl(&w->pub.sinks[1]);
w->ignoreAlpha.store(ignoreAlphaVal ? (bool) ignoreAlphaVal->data.vec4[0] : true);
const char *expectedTitle = CHi_Crawl(&w->pub.sinks[0])->data.text;
if(w->lastWindowString == nullptr || strcmp(w->lastWindowString, expectedTitle)) {
if(w->lastWindowString) {
free(w->lastWindowString);
}
w->lastWindowString = strdup(expectedTitle);
w->config = SL::Screen_Capture::CreateCaptureConfiguration([=](){
for(SL::Screen_Capture::Window& window : SL::Screen_Capture::GetWindows()) {
if(!strcmp(window.Name, w->lastWindowString)) {
return std::vector<SL::Screen_Capture::Window>{window};
}
}
return std::vector<SL::Screen_Capture::Window>{};
})->onNewFrame([=](const SL::Screen_Capture::Image& img, const SL::Screen_Capture::Window& window){
CHiImage *new_image = CHi_Image_New(2, 4, (window.Size.x * 8 + 15) & ~15, window.Size.x, window.Size.y, nullptr);
memset(new_image->data8, 0, new_image->stride * new_image->height);
bool ignoreAlpha = w->ignoreAlpha.load();
#pragma omp parallel for
for(size_t y = 0; y < new_image->height; y++) {
uint8_t buf[16] = {};
for(size_t x = 0; x < new_image->width; x += 2) {
memcpy(buf, &img.Data[y * new_image->width + x], 8);
__m128i c = _mm_loadu_si128((__m128i*) buf);
c = _mm_shuffle_epi8(c, _mm_set_epi8(7, -128, 6, -128, 5, -128, 4, -128, 3, -128, 2, -128, 1, -128, 0, -128));
if(ignoreAlpha) {
c = _mm_or_si128(c, _mm_set_epi16(0xFFFF, 0, 0, 0, 0xFFFF, 0, 0, 0));
}
c = apply_gamma_epi16(c, _mm_set_ps(1, 2.2f, 2.2f, 2.2f));
_mm_store_si128((__m128i*) ((uintptr_t) new_image->data8 + y * new_image->stride + x * 8), c);
}
}
std::unique_lock<std::mutex> lock{w->mut};
while(w->images.size() > 0) {
CHi_Image_Free(w->images.front());
w->images.erase(w->images.begin());
}
w->images.push_back(new_image);
})->start_capturing();
}
std::unique_lock<std::mutex> lock{w->mut};
if(w->images.size() > 0) {
if(n->sources[0].data.sample) {
CHi_Image_Free(n->sources[0].data.sample);
}
n->sources[0].type = CUTIHI_VAL_SAMPLE;
n->sources[0].data.sample = w->images.front();
w->images.erase(w->images.begin());
}
MTR_END("CHi", "window_perform");
return 1;
}
static void window_destroy(CHiPubNode *pubn) {
CHiWindowNode *n = (CHiWindowNode*) pubn;
delete n;
}
CUTIVIS CHiPubNode *CHi_Window() {
auto *n = new CHiWindowNode();
n->pub.type = CUTIHI_T('CWin','dow ');
n->pub.Start = n->pub.Stop = NULL;
n->pub.Perform = window_perform;
n->pub.Destroy = window_destroy;
n->pub.sinkCount = 1;
n->pub.sinks = (CHiValue*) calloc(sizeof(*n->pub.sinks), 2);
n->pub.sourceCount = 1;
n->pub.sources = (CHiValue*) calloc(sizeof(*n->pub.sources), 1);
return &n->pub;
}
CUTIVIS size_t CHi_Window_GetList(void **buf) {
auto vec = SL::Screen_Capture::GetWindows();
*buf = calloc(vec.size(), sizeof(vec[0]));
memcpy(*buf, &vec[0], sizeof(vec[0]) * vec.size());
return vec.size();
}
CUTIVIS const char *CHi_Window_GetName(void *buf, size_t i) {
return ((SL::Screen_Capture::Window*) buf)[i].Name;
}
CUTIVIS size_t CHi_Window_GetHandle(void *buf, size_t i) {
return ((SL::Screen_Capture::Window*) buf)[i].Handle;
}
CUTIVIS void CHi_Window_FreeList(void *buf) {
free(buf);
}

View File

@@ -20,8 +20,8 @@
#include"minitrace.h"
static Display *d;
static Window root;
static thread_local Display *d;
static thread_local Window root;
static int find_window(Display *d, Window *w, const char *contains) {
if(contains) {
@@ -40,7 +40,7 @@ static int find_window(Display *d, Window *w, const char *contains) {
for(int i = 0; i < numItems; i++) {
status = XGetWMName(d, list[i], &windowName);
if(status >= Success) {
if(windowName.value && strstr(windowName.value, contains)) {
if(windowName.value && strstr((const char*) windowName.value, contains)) {
*w = list[i];
found = 1;
break;
@@ -59,17 +59,20 @@ static int find_window(Display *d, Window *w, const char *contains) {
}
typedef struct {
CHiPubNode pub;
Window xcache;
XImage *ximg;
XShmSegmentInfo shminfo;
CHiImage *vcache;
} CHiWindowNode;
} WindowImpl;
static int window_perform(CHiPubNode *n) {
CHiWindowNode *w = (CHiWindowNode*) n;
auto *w = (WindowImpl*) n->impl;
if(!d) {
d = XOpenDisplay(NULL);
root = RootWindow(d, DefaultScreen(d));
}
MTR_BEGIN("CHi", "window_perform");
@@ -86,11 +89,11 @@ static int window_perform(CHiPubNode *n) {
w->ximg = XShmCreateImage(d, attrs.visual, 32, ZPixmap, NULL, &w->shminfo, attrs.width, attrs.height);
stride = ((w->ximg->bytes_per_line + 15) & ~15);
w->shminfo.shmid = shmget(IPC_PRIVATE, stride * w->ximg->height, IPC_CREAT | 0777);
w->shminfo.shmaddr = w->ximg->data = shmat(w->shminfo.shmid, 0, 0);
w->shminfo.shmaddr = w->ximg->data = (char*) shmat(w->shminfo.shmid, 0, 0);
w->shminfo.readOnly = False;
XShmAttach(d, &w->shminfo);
w->vcache = CHi_Image_New(2, 4, 8 * attrs.width, attrs.width, attrs.height, NULL);
w->vcache = CHi_Image_New(2, 4, (8 * attrs.width + 15) & ~15, attrs.width, attrs.height, NULL);
} else {
stride = ((w->ximg->bytes_per_line + 15) & ~15);
}
@@ -100,12 +103,17 @@ static int window_perform(CHiPubNode *n) {
XShmGetImage(d, w->xcache, w->ximg, 0, 0, AllPlanes);
bool ignoreAlpha = CHi_Crawl(&n->sinks[1])->data.vec4[0] != 0;
// Turn u8 image to u16
#pragma omp parallel for
for(size_t y = 0; y < w->vcache->height; y++) {
for(size_t x = 0; x < w->vcache->width; x += 2) {
__m128i c = _mm_loadu_si128((__m128i*) ((uintptr_t) w->ximg->data + y * w->ximg->bytes_per_line + x * 4));
c = _mm_shuffle_epi8(c, _mm_set_epi8(7, -128, 6, -128, 5, -128, 4, -128, 3, -128, 2, -128, 1, -128, 0, -128));
if(ignoreAlpha) {
c = _mm_or_si128(c, _mm_set_epi16(0xFFFF, 0, 0, 0, 0xFFFF, 0, 0, 0));
}
c = apply_gamma_epi16(c, _mm_set_ps(1, 2.2f, 2.2f, 2.2f));
_mm_storeu_si128((__m128i*) ((uintptr_t) w->vcache->data16 + y * w->vcache->stride + x * 8), c);
}
@@ -120,7 +128,7 @@ static int window_perform(CHiPubNode *n) {
}
static void window_destroy(CHiPubNode *pubn) {
CHiWindowNode *n = (void*) pubn;
auto *n = (WindowImpl*) pubn->impl;
if(n->vcache) {
XShmDetach(d, &n->shminfo);
@@ -132,45 +140,25 @@ static void window_destroy(CHiPubNode *pubn) {
}
CUTIVIS CHiPubNode *CHi_Window() {
CHiPubNode *n = CHi_AllocNode(CUTIHI_T('CWin','dow '), 2, 1, sizeof(WindowImpl));
n->Start = n->Stop = NULL;
n->Perform = window_perform;
n->Destroy = window_destroy;
return n;
}
// All of the following are ews
struct WindowListDatum {
Window handle;
char name[128];
};
CUTIVIS size_t CHi_Window_GetList(void **buf) {
if(!d) {
d = XOpenDisplay(NULL);
root = RootWindow(d, DefaultScreen(d));
}
CHiWindowNode *n = calloc(1, sizeof(*n));
n->pub.type = CUTIHI_T('CWin','dow ');
n->pub.Start = n->pub.Stop = NULL;
n->pub.Perform = window_perform;
n->pub.Destroy = window_destroy;
n->pub.sinkCount = 1;
n->pub.sinks = calloc(sizeof(*n->pub.sinks), 1);
n->pub.sourceCount = 1;
n->pub.sources = calloc(sizeof(*n->pub.sources), 1);
n->xcache = 0;
n->vcache = NULL;
return &n->pub;
}
// All of the following are ews
CUTIVIS size_t CHi_Window_GetSourceCount() {
Atom atom = XInternAtom(d, "_NET_CLIENT_LIST", 1);
Atom actualType;
int format;
unsigned long numItems, bytesAfter;
Window *list;
int status = XGetWindowProperty(d, root, atom, 0L, ~0L, 0, AnyPropertyType, &actualType, &format, &numItems, &bytesAfter, (unsigned char**) &list);
//XFree(list);
return status >= Success ? numItems : 0;
}
CUTIVIS const char *CHi_Window_GetSourceName(size_t idx) {
int found = 0;
Atom atom = XInternAtom(d, "_NET_CLIENT_LIST", 1);
Atom actualType;
@@ -183,36 +171,35 @@ CUTIVIS const char *CHi_Window_GetSourceName(size_t idx) {
int status = XGetWindowProperty(d, root, atom, 0L, ~0L, 0, AnyPropertyType, &actualType, &format, &numItems, &bytesAfter, (unsigned char**) &list);
if(status >= Success) {
//XFree(list);
WindowListDatum *data = (WindowListDatum*) calloc(numItems, sizeof(*data));
size_t successfulWindows = 0;
status = XGetWMName(d, list[idx], &windowName);
if(status >= Success) {
found = 1;
for(size_t i = 0; i < numItems; i++) {
status = XGetWMName(d, list[i], &windowName);
if(status >= Success) {
data[successfulWindows].handle = list[i];
strncpy(data[successfulWindows].name, (char*) windowName.value, sizeof(data[successfulWindows].name));
successfulWindows++;
}
}
XFree(list);
*buf = data;
return successfulWindows;
}
return found ? strdup(windowName.value ? windowName.value : "") : NULL;
}
CUTIVIS uintptr_t CHi_Window_GetSourceData(size_t idx) {
Atom atom = XInternAtom(d, "_NET_CLIENT_LIST", 1);
Atom actualType;
int format;
unsigned long numItems, bytesAfter;
Window *list;
int status = XGetWindowProperty(d, root, atom, 0L, ~0L, 0, AnyPropertyType, &actualType, &format, &numItems, &bytesAfter, (unsigned char**) &list);
if(status >= Success) {
Window ret = list[idx];
//XFree(list);
return ret;
}
*buf = nullptr;
return 0;
}
CUTIVIS size_t CHi_Window_GetNextSource(size_t i) {
return i + 1;
CUTIVIS const char *CHi_Window_GetName(void *buf, size_t i) {
return ((WindowListDatum*) buf)[i].name;
}
CUTIVIS size_t CHi_Window_GetHandle(void *buf, size_t i) {
return ((WindowListDatum*) buf)[i].handle;
}
CUTIVIS void CHi_Window_FreeList(void *buf) {
free(buf);
}

View File

@@ -22,13 +22,32 @@
#include"timeline.h"
#include<functional>
#include<algorithm>
#include<eebie/writer.h>
#define SSE_MATHFUN_WITH_CODE
#include<hi/kumb.h>
#include<hi/linearity.h>
static Frame *globaldis;
extern Frame *globaldis;
// The Preview node is actually a NOOP
// Must inject our own function when either loading or creating projects
static int INJECTED_PREVIEW_FUNC(CHiPubNode *preview) {
CHiValue *val = CHi_Crawl(&preview->sinks[0]);
if(val->type == CUTIHI_VAL_SAMPLE && val->data.sample) {
float t = CHi_Time_Get(preview->ng);
globaldis->CallAfter([=](){
globaldis->viewer->SetImage(val->data.sample);
globaldis->timeline->Refresh();
globaldis->stba->SetStatusText(wxString::Format("%02i:%02i:%06.03fs", (int) (t / 3600), (int) (t / 60), fmodf(t, 60)));
});
}
return 1;
};
std::string node_name_from_id(uint64_t id) {
static std::unordered_map<uint64_t, std::string> NODE_ID_NAMES = {
@@ -38,7 +57,7 @@ std::string node_name_from_id(uint64_t id) {
{CUTIHI_T('CTim','e '), "Time"},
{CUTIHI_T('CMod','ulat'), "Modulate"},
{CUTIHI_T('CCns','tCol'), "Constant"},
{CUTIHI_T('CEmb','ed '), "Frame"},
{CUTIHI_T('CEmb','ed '), "Layer"},
{CUTIHI_T('CIma','ge '), "Image"},
{CUTIHI_T('CWin','dow '), "Window"},
{CUTIHI_T('CInA','udio'), "Microphone"},
@@ -55,6 +74,7 @@ std::string node_name_from_id(uint64_t id) {
{CUTIHI_T('CChr','omaK'), "Chroma Key"},
{CUTIHI_T('CStr','RTMP'), "Stream RTMP"},
{CUTIHI_T('CEnc','AACL'), "Encode AAC-LC"},
{CUTIHI_T('CExp','Mkv '), "Mux Matroska"},
};
auto nameit = NODE_ID_NAMES.find(id);
@@ -85,10 +105,9 @@ static void ShapeGrNode(GrNode *gn) {
gn->sources = {{"Audio", GrNode::Port::Type::SAMPLE}};
int numSinks = gn->logical->sinkCount;
while(numSinks > 0 && gn->logical->sinks[numSinks - 1].type == CUTIHI_VAL_NONE) {
numSinks--;
if(gn->logical->sinks[numSinks - 1].type == CUTIHI_VAL_NONE) {
numSinks++;
}
numSinks++;
gn->sinks.clear();
for(int s = 0; s < numSinks; s++) {
@@ -108,7 +127,7 @@ static void ShapeGrNode(GrNode *gn) {
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
} else if(gn->logical->type == CUTIHI_T('CEmb','ed ')) {
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
gn->sinks = {{"Frame", GrNode::Port::Type::SAMPLE, true}};
gn->sinks = {{"Background", GrNode::Port::Type::SAMPLE, true}};
int numSubs = 0;
while(gn->logical->sinkCount > (1 + numSubs * 3 + 0) && gn->logical->sinks[1 + numSubs * 3 + 0].type != CUTIHI_VAL_NONE) {
@@ -122,19 +141,19 @@ static void ShapeGrNode(GrNode *gn) {
gn->sinks.push_back({" Size", GrNode::Port::Type::VEC1, true});
}
} else if(gn->logical->type == CUTIHI_T('CIma','ge ')) {
gn->sinks = {{"Filepath", GrNode::Port::Type::FILE_OPEN}};
gn->sinks = {{"Filepath", GrNode::Port::Type::FILE_OPENING}};
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
} else if(gn->logical->type == CUTIHI_T('CWin','dow ')) {
gn->sinks = {{"Name", GrNode::Port::Type::WINDOW_SOURCE}};
gn->sinks = {{"Name", GrNode::Port::Type::WINDOW_SOURCE}, {"Ignore Alpha", GrNode::Port::Type::CHECKBOX}};
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
} else if(gn->logical->type == CUTIHI_T('CInA','udio')) {
gn->sinks = {{"Source", GrNode::Port::Type::MIC_SOURCE}};
gn->sources = {{"Audio", GrNode::Port::Type::SAMPLE}};
} else if(gn->logical->type == CUTIHI_T('CExp','Wave')) {
gn->sinks = {{"Filename", GrNode::Port::Type::FILE_SAVE}, {"Audio", GrNode::Port::Type::SAMPLE}};
gn->sinks = {{"Filename", GrNode::Port::Type::FILE_SAVING}, {"Audio", GrNode::Port::Type::SAMPLE}};
gn->sources = {};
} else if(gn->logical->type == CUTIHI_T('CMov','ie ')) {
gn->sinks = {{"Filepath", GrNode::Port::Type::FILE_OPEN}, {"Time", GrNode::Port::Type::VEC1}};
gn->sinks = {{"Filepath", GrNode::Port::Type::FILE_OPENING}, {"Time", GrNode::Port::Type::VEC1}};
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}, {"Audio", GrNode::Port::Type::SAMPLE}};
} else if(gn->logical->type == CUTIHI_T('CEnc','GVP8')) {
gn->sinks = {{"Sample", GrNode::Port::Type::SAMPLE}};
@@ -146,7 +165,7 @@ static void ShapeGrNode(GrNode *gn) {
gn->sinks = {{"Sample", GrNode::Port::Type::SAMPLE}};
gn->sources = {{"Bitstream"}};
} else if(gn->logical->type == CUTIHI_T('CExp','Webm')) {
gn->sinks = {{"Video Bitstream"}, {"Audio Bitstream"}, {"Filename", GrNode::Port::Type::FILE_SAVE}};
gn->sinks = {{"Video Bitstream"}, {"Audio Bitstream"}, {"Filename", GrNode::Port::Type::FILE_SAVING}};
gn->sources = {};
} else if(gn->logical->type == CUTIHI_T('CKey','hook')) {
gn->sinks = {{"Key", GrNode::Port::Type::TEXT}, {"Smooth Time", GrNode::Port::Type::VEC1}};
@@ -169,6 +188,9 @@ static void ShapeGrNode(GrNode *gn) {
} else if(gn->logical->type == CUTIHI_T('CEnc','AACL')) {
gn->sinks = {{"Audio", GrNode::Port::Type::SAMPLE}};
gn->sources = {{"Bitstream"}};
} else if(gn->logical->type == CUTIHI_T('CExp','Mkv ')) {
gn->sinks = {{"Video Bitstream"}, {"Audio Bitstream"}, {"Filename", GrNode::Port::Type::FILE_SAVING}};
gn->sources = {};
}
gn->Fit();
@@ -220,76 +242,40 @@ Frame::Frame() : wxFrame(NULL, wxID_ANY, "Cuticle", wxDefaultPosition, {wxSystem
wxFileDialog save{this, "Save", "", "", "Cuticle Project (*.ctc)|*.ctc", wxFD_SAVE | wxFD_OVERWRITE_PROMPT};
if(save.ShowModal() == wxID_OK) {
FILE *f = fopen(save.GetPath().mb_str(), "wb");
struct UD {
FILE *f;
Frame *frame;
} ud;
CHi_NodeGraphSave(graph->backendNG, +[](void *ud, const void *data, size_t len){
auto f = (FILE*) ud;
return fwrite(data, 1, len, f);
}, f);
for(size_t n = 0; n < graph->backendNG->count; n++) {
GrNode *gn = graph->get_graphical(graph->backendNG->nodes[n]);
int32_t pos[2] = {gn->GetPosition().x, gn->GetPosition().y};
fwrite(pos, sizeof(pos), 1, f);
wxString path = save.GetPath();
if(!path.EndsWith(".ctc")) {
path += ".ctc";
}
fclose(f);
ud.f = fopen(path.mb_str(), "wb");
ud.frame = this;
CHi_NodeGraphSave(graph->backendNG, +[](void *udPtr, const void *data, size_t len){
auto &ud = *(UD*) udPtr;
return fwrite(data, 1, len, ud.f);
}, +[](void *udPtr, EBMLWriter *ebml, CHiPubNode *node){
auto &ud = *(UD*) udPtr;
GrNode *gn = ud.frame->graph->get_graphical(node);
int32_t pos[2] = {gn->GetPosition().x, gn->GetPosition().y};
ebml_writer_push(ebml, 0x5000);
ebml_writer_put(ebml, 0x5001, EBML_BINARY, (EBMLPrimitive) {.binary = {.ptr = (uint8_t*) &pos, sizeof(pos)}});
ebml_writer_pop(ebml);
}, &ud);
fclose(ud.f);
}
} else if(ev.GetId() == wxID_OPEN) {
wxFileDialog load{this, "Load", "", "", "Cuticle Project (*.ctc)|*.ctc", wxFD_OPEN | wxFD_FILE_MUST_EXIST};
if(load.ShowModal() == wxID_OK) {
FILE *f = fopen(load.GetPath().mb_str(), "rb");
if(CHi_NodeGraphLoad(graph->backendNG, +[](void *ud, void *data, size_t len){
auto f = (FILE*) ud;
return fread(data, 1, len, f);
}, f) == 0) {
for(GrNode *gnode : graph->gnodes) {
gnode->Destroy();
}
graph->gnodes.clear();
graph->links.clear();
for(size_t n = 0; n < graph->backendNG->count; n++) {
GrNode *gnode = new GrNode(graph);
int32_t pos[2];
fread(pos, sizeof(pos), 1, f);
gnode->SetPosition({pos[0], pos[1]});
gnode->logical = graph->backendNG->nodes[n];
ShapeGrNode(gnode);
graph->gnodes.push_back(gnode);
}
for(size_t n = 0; n < graph->backendNG->count; n++) {
CHiPubNode *node = graph->backendNG->nodes[n];
for(size_t s = 0; s < node->sinkCount; s++) {
if(node->sinks[s].type == CUTIHI_VAL_LINKED) {
graph->links.push_back(NodeGraph::Link{graph->gnodes[n], s, *std::find_if(graph->gnodes.begin(), graph->gnodes.end(), [=](GrNode *gn){ return gn->logical == node->sinks[s].data.linked.to; }), node->sinks[s].data.linked.idx});
}
}
}
toolbar.duration->SetSeconds(std::max(0.f, graph->backendNG->duration));
if(graph->backendNG->duration <= 0) {
toolbar.durationEnable->SetValue(false);
toolbar.duration->Enable(false);
}
}
fclose(f);
LoadProject(load.GetPath().utf8_string());
}
}
});
@@ -321,21 +307,6 @@ Frame::Frame() : wxFrame(NULL, wxID_ANY, "Cuticle", wxDefaultPosition, {wxSystem
} else {
CHi_BeginCompilation(graph->backendNG);
toolbar.btnPerform->SetLabel("Kill");
std::thread{[=](){
while(graph->backendNG->compilationStatus == CUTIHI_COMP_RUNNING) {
CallAfter([=](){
float t = CHi_Time_Get(graph->backendNG);
stba->SetStatusText(wxString::Format("%02i:%02i:%06.03fs", (int) (t / 3600), (int) (t / 60), fmodf(t, 60)));
});
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
CallAfter([=](){
stba->SetStatusText("Compilation has ended.");
});
}}.detach();
}
});
@@ -343,27 +314,6 @@ Frame::Frame() : wxFrame(NULL, wxID_ANY, "Cuticle", wxDefaultPosition, {wxSystem
tlba->AddControl(toolbar.duration);
tlba->AddControl(toolbar.btnPerform);
graph->backendNG->eventOnStopComplete = +[](CHiNodeGraph *ng){
wxTheApp->CallAfter([ng](){
wxButton *btn = ((Frame*) ng->ud)->toolbar.btnPerform;
btn->Enable();
btn->SetLabel("Compile");
auto tlba = ((Frame*) ng->ud)->tlba;
tlba->EnableTool(wxID_SAVE, true);
tlba->EnableTool(wxID_OPEN, true);
});
};
graph->backendNG->eventOnError = +[](CHiNodeGraph *ng, CHiPubNode *n) {
wxTheApp->CallAfter([ng, n](){
auto frame = (Frame*) ng->ud;
GrNode *gn = frame->graph->get_graphical(n);
gn->Refresh();
});
};
tlba->Realize();
aui.SetFlags(wxAUI_MGR_LIVE_RESIZE | wxAUI_MGR_DEFAULT);
@@ -371,6 +321,7 @@ Frame::Frame() : wxFrame(NULL, wxID_ANY, "Cuticle", wxDefaultPosition, {wxSystem
Centre();
LoadProject("");
graph->CreatePreviewNode();
}
@@ -378,6 +329,104 @@ Frame::~Frame() {
aui.UnInit();
}
void Frame::LoadProject(std::string filename) {
struct UD {
FILE *f;
Frame *frame;
std::unordered_map<uint64_t, wxPoint> nodePositions;
} ud = {};
if(filename != "") {
ud.f = fopen(filename.c_str(), "rb");
}
ud.frame = this;
if(filename == "" || CHi_NodeGraphLoad(graph->backendNG, +[](void *udPtr, void *data, size_t len){
auto &ud = *(UD*) udPtr;
return fread(data, 1, len, ud.f);
}, +[](void *udPtr, uint64_t nodeIdx, uint64_t elId, const void *data, size_t len){
auto &ud = *(UD*) udPtr;
if(elId == 0x5001) {
auto pos = (const int32_t*) data;
ud.nodePositions.emplace(nodeIdx, wxPoint{pos[0], pos[1]});
}
}, &ud) == 0) {
for(GrNode *gnode : graph->gnodes) {
gnode->Destroy();
}
graph->gnodes.clear();
graph->links.clear();
for(size_t n = 0; n < graph->backendNG->count; n++) {
GrNode *gnode = new GrNode(graph);
gnode->SetPosition(ud.nodePositions[n]);
gnode->logical = graph->backendNG->nodes[n];
ShapeGrNode(gnode);
if(gnode->logical->type == CUTIHI_T('CPre', 'view')) {
gnode->logical->Perform = INJECTED_PREVIEW_FUNC;
}
graph->gnodes.push_back(gnode);
}
for(size_t n = 0; n < graph->backendNG->count; n++) {
CHiPubNode *node = graph->backendNG->nodes[n];
for(size_t s = 0; s < node->sinkCount; s++) {
if(node->sinks[s].linked.to) {
graph->links.push_back(NodeGraph::Link{graph->gnodes[n], s, *std::find_if(graph->gnodes.begin(), graph->gnodes.end(), [=](GrNode *gn){ return gn->logical == node->sinks[s].linked.to; }), node->sinks[s].linked.idx});
}
}
}
toolbar.duration->SetSeconds(std::max(0.f, graph->backendNG->duration));
if(graph->backendNG->duration <= 0) {
toolbar.durationEnable->SetValue(false);
toolbar.duration->Enable(false);
}
timeline->ResetRows();
graph->backendNG->eventOnStopComplete = +[](CHiNodeGraph *ng){
wxTheApp->CallAfter([ng](){
wxButton *btn = ((Frame*) ng->ud)->toolbar.btnPerform;
btn->Enable();
btn->SetLabel("Compile");
auto tlba = ((Frame*) ng->ud)->tlba;
tlba->EnableTool(wxID_SAVE, true);
tlba->EnableTool(wxID_OPEN, true);
auto stba = ((Frame*) ng->ud)->stba;
stba->SetStatusText("Compilation has ended.");
});
};
graph->backendNG->eventOnError = +[](CHiNodeGraph *ng, CHiPubNode *n) {
wxTheApp->CallAfter([ng, n](){
auto frame = (Frame*) ng->ud;
GrNode *gn = frame->graph->get_graphical(n);
gn->Refresh();
});
};
}
if(ud.f) {
fclose(ud.f);
}
// Force everything in the graph to be computed so the preview immediately shows up
CHi_Hysteresis(graph->backendNG);
}
bool GrNode::MouseOverPort(wxPoint point, bool &source, int &i) {
if(point.y < 26 || point.x < 0 || point.x > GetSize().x) return false;
@@ -396,7 +445,7 @@ void GrNode::MakeKeyframe(int sinkIdx) {
CHi_MakeKeyframe(ng->backendNG, this->logical, sinkIdx);
((Frame*) ng->GetParent())->timeline->Refresh();
globaldis->timeline->ResetRows();
}
static bool has_errors(CHiPubNode *pn) {
@@ -429,8 +478,8 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80})
int y = 13;
int i = 0;
for(Port &p : sinks) {
wxColour col = p.type == GrNode::Port::Type::FILE_OPEN ? wxColour{255, 0, 0}
: p.type == GrNode::Port::Type::FILE_SAVE ? wxColour{255, 0, 0}
wxColour col = p.type == GrNode::Port::Type::FILE_OPENING ? wxColour{255, 0, 0}
: p.type == GrNode::Port::Type::FILE_SAVING ? wxColour{255, 0, 0}
: p.type == GrNode::Port::Type::COLOR ? wxColour{0, 0, 255}
: p.type == GrNode::Port::Type::VEC2 ? wxColour{0, 255, 0}
: p.type == GrNode::Port::Type::TEXT ? wxColour{255, 255, 0}
@@ -457,8 +506,8 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80})
y = 13;
i = 0;
for(Port &p : sources) {
wxColour col = p.type == GrNode::Port::Type::FILE_OPEN ? wxColour{255, 0, 0}
: p.type == GrNode::Port::Type::FILE_SAVE ? wxColour{255, 0, 0}
wxColour col = p.type == GrNode::Port::Type::FILE_OPENING ? wxColour{255, 0, 0}
: p.type == GrNode::Port::Type::FILE_SAVING ? wxColour{255, 0, 0}
: p.type == GrNode::Port::Type::COLOR ? wxColour{0, 0, 255}
: p.type == GrNode::Port::Type::VEC2 ? wxColour{0, 255, 0}
: p.type == GrNode::Port::Type::TEXT ? wxColour{255, 255, 0}
@@ -584,7 +633,7 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80})
if(dlg.ShowModal() == wxID_OK) {
pthread_mutex_lock(&this->logical->ng->mut);
CHiValue newv;
CHiValue newv = {};
newv.type = CUTIHI_VAL_VEC4;
newv.data.vec4[0] = dlg.GetColourData().GetColour().Red() / 255.f;
newv.data.vec4[1] = dlg.GetColourData().GetColour().Green() / 255.f;
@@ -595,12 +644,12 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80})
pthread_mutex_unlock(&this->logical->ng->mut);
}
} else if(sinks[p].type == Port::Type::FILE_OPEN) {
} else if(sinks[p].type == Port::Type::FILE_OPENING) {
wxFileDialog dlg(this, wxFileSelectorPromptStr, wxEmptyString, wxEmptyString, wxFileSelectorDefaultWildcardStr, wxFD_OPEN | wxFD_PREVIEW);
if(dlg.ShowModal() == wxID_OK) {
pthread_mutex_lock(&this->logical->ng->mut);
CHiValue newv;
CHiValue newv = {};
newv.type = CUTIHI_VAL_TEXT;
newv.data.text = strdup(dlg.GetPath().utf8_str());
CHi_ConfigureSink(this->logical, p, newv);
@@ -608,12 +657,12 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80})
pthread_mutex_unlock(&this->logical->ng->mut);
}
} else if(sinks[p].type == Port::Type::FILE_SAVE) {
} else if(sinks[p].type == Port::Type::FILE_SAVING) {
wxFileDialog dlg(this, wxFileSelectorPromptStr, wxEmptyString, wxEmptyString, wxFileSelectorDefaultWildcardStr, wxFD_SAVE | wxFD_PREVIEW | wxFD_OVERWRITE_PROMPT);
if(dlg.ShowModal() == wxID_OK) {
pthread_mutex_lock(&this->logical->ng->mut);
CHiValue newv;
CHiValue newv = {};
newv.type = CUTIHI_VAL_TEXT;
newv.data.text = strdup(dlg.GetPath().utf8_str());
CHi_ConfigureSink(this->logical, p, newv);
@@ -624,28 +673,29 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80})
} else if(sinks[p].type >= Port::Type::VEC1 && sinks[p].type <= Port::Type::VEC4) {
auto ctrls = std::make_shared<std::vector<wxTextCtrl*>>();
for(int i = 0; i <= (int) sinks[p].type - (int) Port::Type::VEC1; i++) {
wxTextCtrl *tc = new wxTextCtrl(GetParent(), wxID_ANY, wxString::Format("%f", this->logical->sinks[p].data.vec4[i]), GetParent()->ScreenToClient(ClientToScreen({5 + 60 * i, (p + 1) * 20})));
wxTextCtrl *tc = new wxTextCtrl(GetParent(), wxID_ANY, wxString::Format("%f", this->logical->sinks[p].data.vec4[i]), GetParent()->ScreenToClient(ClientToScreen({5 + 60 * i, (p + 1) * 20})), wxDefaultSize, wxTE_PROCESS_ENTER);
tc->Bind(wxEVT_TEXT_ENTER, [=](wxCommandEvent &ev){
double d;
if(tc->GetValue().ToDouble(&d)) {
pthread_mutex_lock(&this->logical->ng->mut);
CHiValue newv = *CHi_Crawl(&this->logical->sinks[p]);
newv.type = CUTIHI_VAL_VEC4;
newv.data.vec4[i] = d;
CHi_ConfigureSink(this->logical, p, newv);
auto it = std::find(ctrls->begin(), ctrls->end(), tc);
ctrls->operator[]((it - ctrls->begin() + 1) % ctrls->size())->SetFocus();
ctrls->erase(it);
CallAfter([tc](){tc->Destroy();});
parent->Dirtify(this);
pthread_mutex_unlock(&this->logical->ng->mut);
}
});
tc->Bind(wxEVT_KEY_DOWN, [=](wxKeyEvent &ev){
if(ev.GetKeyCode() == WXK_RETURN) {
double d;
if(tc->GetValue().ToDouble(&d)) {
pthread_mutex_lock(&this->logical->ng->mut);
CHiValue newv = *CHi_Crawl(&this->logical->sinks[p]);
newv.type = CUTIHI_VAL_VEC4;
newv.data.vec4[i] = d;
CHi_ConfigureSink(this->logical, p, newv);
auto it = std::find(ctrls->begin(), ctrls->end(), tc);
ctrls->operator[]((it - ctrls->begin() + 1) % ctrls->size())->SetFocus();
ctrls->erase(it);
CallAfter([tc](){tc->Destroy();});
parent->Dirtify(this);
pthread_mutex_unlock(&this->logical->ng->mut);
}
} else if(ev.GetKeyCode() == WXK_TAB) {
if(ev.GetKeyCode() == WXK_TAB) {
ctrls->operator[]((i + ctrls->size() + (wxGetKeyState(WXK_SHIFT) ? -1 : 1)) % ctrls->size())->SetFocus();
parent->Dirtify(this);
@@ -655,13 +705,13 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80})
}
ctrls->operator[](0)->SetFocus();
} else if(sinks[p].type == Port::Type::TEXT) {
wxTextCtrl *ctrl = new wxTextCtrl(GetParent(), wxID_ANY, this->logical->sinks[p].data.text, GetParent()->ScreenToClient(ClientToScreen({5, (p + 1) * 26})));
wxTextCtrl *ctrl = new wxTextCtrl(GetParent(), wxID_ANY, this->logical->sinks[p].data.text, GetParent()->ScreenToClient(ClientToScreen({5, (p + 1) * 26})), wxDefaultSize, wxTE_PROCESS_ENTER);
ctrl->SetValue(wxString{CHi_Crawl(&this->logical->sinks[p])->data.text});
ctrl->SetFocus();
ctrl->Bind(wxEVT_KILL_FOCUS, [=](wxFocusEvent &ev){
pthread_mutex_lock(&this->logical->ng->mut);
CHiValue newv;
CHiValue newv = {};
newv.type = CUTIHI_VAL_TEXT;
char *c = (char*) malloc(ctrl->GetValue().Len() + 1);
memcpy(c, ctrl->GetValue().c_str(), ctrl->GetValue().Len() + 1);
@@ -672,21 +722,19 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80})
pthread_mutex_unlock(&this->logical->ng->mut);
});
ctrl->Bind(wxEVT_KEY_DOWN, [=](wxKeyEvent &ev){
if(ev.GetKeyCode() == WXK_RETURN) {
pthread_mutex_lock(&this->logical->ng->mut);
ctrl->Bind(wxEVT_TEXT_ENTER, [=](wxCommandEvent &ev){
pthread_mutex_lock(&this->logical->ng->mut);
CHiValue newv;
newv.type = CUTIHI_VAL_TEXT;
char *c = (char*) malloc(ctrl->GetValue().Len() + 1);
memcpy(c, ctrl->GetValue().c_str(), ctrl->GetValue().Len() + 1);
newv.data.text = c;
CHi_ConfigureSink(this->logical, p, newv);
CallAfter([ctrl](){ctrl->Destroy();});
parent->Dirtify(this);
CHiValue newv = {};
newv.type = CUTIHI_VAL_TEXT;
char *c = (char*) malloc(ctrl->GetValue().Len() + 1);
memcpy(c, ctrl->GetValue().c_str(), ctrl->GetValue().Len() + 1);
newv.data.text = c;
CHi_ConfigureSink(this->logical, p, newv);
CallAfter([ctrl](){ctrl->Destroy();});
parent->Dirtify(this);
pthread_mutex_unlock(&this->logical->ng->mut);
} else ev.Skip();
pthread_mutex_unlock(&this->logical->ng->mut);
});
} else if(sinks[p].type == Port::Type::MIC_SOURCE) {
std::vector<wxString> choices;
@@ -700,7 +748,7 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80})
if(dlg.ShowModal() == wxID_OK) {
pthread_mutex_lock(&this->logical->ng->mut);
CHiValue newv;
CHiValue newv = {};
newv.type = CUTIHI_VAL_TEXT;
newv.data.text = strdup(CHi_Microphone_GetSourceName((size_t) (uintptr_t) dlg.GetSelectionData()));
CHi_ConfigureSink(this->logical, p, newv);
@@ -712,8 +760,10 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80})
std::vector<const char*> choicesOrig;
std::vector<wxString> choices;
for(size_t i = CHi_Window_GetNextSource(-1); i < CHi_Window_GetSourceCount(); i = CHi_Window_GetNextSource(i)) {
auto name = CHi_Window_GetSourceName(i);
void *wbuf;
size_t windowCount = CHi_Window_GetList(&wbuf);
for(size_t i = 0; i < windowCount; i++) {
const char *name = CHi_Window_GetName(wbuf, i);
choicesOrig.push_back(name);
choices.push_back(wxString::FromUTF8(name));
@@ -723,7 +773,7 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80})
if(dlg.ShowModal() == wxID_OK) {
pthread_mutex_lock(&this->logical->ng->mut);
CHiValue newv;
CHiValue newv = {};
newv.type = CUTIHI_VAL_TEXT;
newv.data.text = strdup(choicesOrig[dlg.GetSelection()]);
CHi_ConfigureSink(this->logical, p, newv);
@@ -731,6 +781,28 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80})
pthread_mutex_unlock(&this->logical->ng->mut);
}
CHi_Window_FreeList(wbuf);
} else if(sinks[p].type == Port::Type::CHECKBOX) {
wxCheckBox *cb = new wxCheckBox(this, wxID_ANY, sinks[p].name);
cb->SetValue((bool) this->logical->sinks[p].data.vec4[0]);
cb->SetFocus();
cb->Bind(wxEVT_KILL_FOCUS, [=](wxFocusEvent &ev){
pthread_mutex_lock(&this->logical->ng->mut);
CHiValue newv = {};
newv.type = CUTIHI_VAL_VEC4;
newv.data.vec4[0] = cb->IsChecked();
newv.data.vec4[1] = cb->IsChecked();
newv.data.vec4[2] = cb->IsChecked();
newv.data.vec4[3] = cb->IsChecked();
CHi_ConfigureSink(this->logical, p, newv);
parent->Dirtify(this);
CallAfter([cb](){cb->Destroy();});
pthread_mutex_unlock(&this->logical->ng->mut);
});
}
}
});
@@ -766,7 +838,7 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80})
if(!isSource) {
pthread_mutex_lock(&daNode->ng->mut);
CHiValue val;
CHiValue val = {};
val.type = CUTIHI_VAL_NONE;
CHi_ConfigureSink(daNode, daPortIdx, val);
@@ -775,6 +847,10 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80})
parent->Dirtify(this);
parent->Refresh();
} else {
auto ng = (NodeGraph*) GetParent();
ng->DestroyNode(this);
}
}
});
@@ -812,7 +888,7 @@ ImageViewer::ImageViewer(Frame *f) : wxPanel(f, wxID_ANY) {
Bind(wxEVT_PAINT, [this](wxPaintEvent &ev){
if(bm.IsOk()) {
wxPaintDC dc(this);
dc.DrawBitmap(bm, pos);
dc.DrawBitmap(bm, pos + (GetSize() - bm.GetSize()) / 2);
}
});
Bind(wxEVT_MIDDLE_DOWN, [this](wxMouseEvent &ev){
@@ -852,8 +928,19 @@ __attribute__((optimize("O3"))) static uint8_t *bgra64torgb24(uint8_t *orig, siz
__m128i z[8] = {};
for(int zi = 0; zi < 8; zi++) {
z[zi] = _mm_loadu_si128((__m128i*) temp + zi);
z[zi] = apply_gamma_epi16(z[zi], _mm_set_ps(1, 1 / 2.2f, 1 / 2.2f, 1 / 2.2f));
// Alpha-blend with checkered background.
__m128i bottom = (((x + zi) / 4 ^ y / 4) & 1)
? _mm_set_epi16(0xFFFF, 0x8000, 0x8000, 0x8000, 0xFFFF, 0x8000, 0x8000, 0x8000)
: _mm_set_epi16(0xFFFF, 0x4000, 0x4000, 0x4000, 0xFFFF, 0x4000, 0x4000, 0x4000);
__m128i top = _mm_loadu_si128((__m128i*) temp + zi);
__m128i alpha = _mm_shuffle_epi8(top, _mm_set_epi8(15, 14, 15, 14, 15, 14, 15, 14, 7, 6, 7, 6, 7, 6, 7, 6));
__m128i invAlpha = _mm_sub_epi16(_mm_set1_epi16(0xFFFF), alpha);
__m128i result = _mm_add_epi16(_mm_mulhi_epu16(top, alpha), _mm_mulhi_epu16(bottom, invAlpha));
z[zi] = apply_gamma_epi16(result, _mm_set_ps(1, 1 / 2.2f, 1 / 2.2f, 1 / 2.2f));
}
__m128i a = _mm_shuffle_epi8(z[0], _mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, -128, 9, 11, 13, 1, 3, 5));
@@ -899,7 +986,7 @@ void ImageViewer::SetImage(CHiImage *chim) {
ResizeImage(siez);
}
void ImageViewer::ResizeImage(float size) {
float w = size, h = (float) bufH / bufW * siez;
float w = size, h = (float) bufH / bufW * size;
if(w <= 1 || h <= 1) {
return;
@@ -922,7 +1009,7 @@ NodeGraph::NodeGraph(Frame *f) : wxPanel(f, wxID_ANY) {
int idEncodeVp9 = menuExports->Append(wxID_ANY, "Encode VP9", "")->GetId();
int idEncodeOpus = menuExports->Append(wxID_ANY, "Encode Opus", "")->GetId();
int idEncodeAAC = menuExports->Append(wxID_ANY, "Encode AAC-LC", "")->GetId();
int idMuxWebm = menuExports->Append(wxID_ANY, "Mux WebM", "")->GetId();
int idMuxMatroska = menuExports->Append(wxID_ANY, "Mux Matroska", "")->GetId();
int idMuxWav = menuExports->Append(wxID_ANY, "Muv Wav", "")->GetId();
int idStreamRTMP = menuExports->Append(wxID_ANY, "Stream RTMP", "")->GetId();
@@ -991,9 +1078,6 @@ NodeGraph::NodeGraph(Frame *f) : wxPanel(f, wxID_ANY) {
} else if(ev.GetId() == idEncodeVp8) {
noed = new GrNode(this);
noed->logical = CHi_EncodeVP8();
} else if(ev.GetId() == idMuxWebm) {
noed = new GrNode(this);
noed->logical = CHi_MuxWebm();
} else if(ev.GetId() == idWindow) {
noed = new GrNode(this);
noed->logical = CHi_Window();
@@ -1033,6 +1117,9 @@ NodeGraph::NodeGraph(Frame *f) : wxPanel(f, wxID_ANY) {
} else if(ev.GetId() == idEncodeAAC) {
noed = new GrNode(this);
noed->logical = CHi_EncodeAAC();
} else if(ev.GetId() == idMuxMatroska) {
noed = new GrNode(this);
noed->logical = CHi_MuxMatroska();
}
if(noed) {
@@ -1044,7 +1131,7 @@ NodeGraph::NodeGraph(Frame *f) : wxPanel(f, wxID_ANY) {
after();
((Frame*) GetParent())->timeline->ResetRows();
globaldis->timeline->ResetRows();
}
pthread_mutex_unlock(&backendNG->mut);
@@ -1110,17 +1197,16 @@ NodeGraph::~NodeGraph() {
void NodeGraph::Alinken(Link l) {
pthread_mutex_lock(&backendNG->mut);
CHiValue newv;
newv.type = CUTIHI_VAL_LINKED;
newv.data.linked.to = l.output->logical;
newv.data.linked.idx = l.o;
CHiValue newv = {};
newv.linked.to = l.output->logical;
newv.linked.idx = l.o;
int err = CHi_ConfigureSink(l.input->logical, l.i, newv);
pthread_mutex_unlock(&backendNG->mut);
if(!err) {
((Frame*) GetParent())->stba->SetStatusText("Uh oh! Hur-hur, there's a WAACKY cycle! Can't do, sorry friend.");
globaldis->stba->SetStatusText("Uh oh! Hur-hur, there's a WAACKY cycle! Can't do, sorry friend.");
return;
}
@@ -1157,7 +1243,7 @@ void NodeGraph::Dirtify(GrNode *g) {
}
}*/
CHi_Hysteresis(gnodes[0]->logical);
CHi_Hysteresis(backendNG);
}
bool operator==(const NodeGraph::Link &l, const NodeGraph::Link &r) {
@@ -1193,17 +1279,23 @@ void NodeGraph::CreatePreviewNode() {
v->SetPosition(wxPoint{GetSize().x - v->GetSize().x - 8, 8});
// The Preview node is actually a NOOP
// Must inject our own function in Perform
v->logical->Perform = +[](CHiPubNode *preview){
CHiValue *val = CHi_Crawl(&preview->sinks[0]);
v->logical->Perform = INJECTED_PREVIEW_FUNC;
}
if(val->type == CUTIHI_VAL_SAMPLE && val->data.sample) {
globaldis->viewer->SetImage(val->data.sample);
void NodeGraph::DestroyNode(GrNode *gn) {
for(auto it = links.begin(); it != links.end();) {
if(it->input == gn || it->output == gn) {
it = links.erase(it);
} else {
it++;
}
}
return 1;
};
gnodes.erase(std::find(gnodes.begin(), gnodes.end(), gn));
wxTheApp->CallAfter([=](){
gn->Destroy();
});
}
GrNode *NodeGraph::get_graphical(CHiPubNode *n) {

View File

@@ -43,13 +43,15 @@ struct Frame : wxFrame {
Frame();
virtual ~Frame();
void LoadProject(std::string filename);
};
struct GrNode : wxPanel {
struct Port {
wxString name;
enum class Type {
NONE, FILE_OPEN, COLOR, VEC1, VEC2, VEC3, VEC4, TEXT, SAMPLE, FILE_SAVE, MIC_SOURCE, WINDOW_SOURCE
NONE, FILE_OPENING, COLOR, VEC1, VEC2, VEC3, VEC4, TEXT, SAMPLE, FILE_SAVING, MIC_SOURCE, WINDOW_SOURCE, CHECKBOX
} type;
bool separator;
@@ -136,6 +138,8 @@ struct NodeGraph : wxPanel {
void CreatePreviewNode();
void DestroyNode(GrNode*);
GrNode *get_graphical(CHiPubNode*);
};

View File

@@ -2,8 +2,13 @@
#include"frame.h"
Frame *globaldis;
struct App : wxApp {
virtual bool OnInit() {
#if wxCHECK_VERSION(3, 3, 0)
SetAppearance(Appearance::System);
#endif
(new Frame())->Show(true);
return true;
}

View File

@@ -10,6 +10,8 @@
#include"frame.h"
extern Frame *globaldis;
static wxBitmap bmpKf;
static wxBitmap bmpKfExtrap;
@@ -38,7 +40,7 @@ bool Timeline::MouseOverKF(wxPoint p, CHiKeyframes* &kfs, size_t &kfIdxRet) {
return false;
}
auto f = (Frame*) GetParent();
auto f = globaldis;
kfs = row->kfs;
@@ -59,7 +61,7 @@ bool Timeline::MouseOverKF(wxPoint p, CHiKeyframes* &kfs, size_t &kfIdxRet) {
void Timeline::ResetRows() {
rows.clear();
auto frame = (Frame*) GetParent();
auto frame = globaldis;
auto kfsList = &frame->graph->backendNG->keyframesList;
@@ -92,13 +94,15 @@ void Timeline::ResetRows() {
y += row.h;
}
Refresh();
}
float Timeline::SnapTime(float t) {
float minDist = FLT_MAX;
float newT = t;
auto f = (Frame*) GetParent();
auto f = globaldis;
std::vector<float> times;
for(Timeline::Row &row : rows) {
@@ -149,7 +153,7 @@ Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) {
Bind(wxEVT_LEFT_DOWN, [=](wxMouseEvent &ev){
Timeline::Row *row = GetRow(ev.GetPosition().y);
auto f = (Frame*) GetParent();
auto f = globaldis;
float t = (ev.GetX() + camX - ZERO_TIME_BASE) / (float) scale;
if(ev.ControlDown()) {
@@ -196,14 +200,14 @@ Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) {
t = SnapTime(t);
}
row->gn->logical->lifespan.end = t;
row->gn->logical->lifespan.end = t < 0 ? 0 : t;
Refresh();
}
});
Bind(wxEVT_MOTION, [=](wxMouseEvent &ev){
auto f = (Frame*) GetParent();
auto f = globaldis;
if(HasCapture()) {
if(captureMode == Timeline::CaptureMode::CAM) {
@@ -255,8 +259,8 @@ Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) {
switch(sinks[i].type) {
case GrNode::Port::Type::TEXT:
case GrNode::Port::Type::FILE_OPEN:
case GrNode::Port::Type::FILE_SAVE:
case GrNode::Port::Type::FILE_OPENING:
case GrNode::Port::Type::FILE_SAVING:
SetToolTip(wxString{val->text});
break;
case GrNode::Port::Type::VEC1:
@@ -319,7 +323,7 @@ Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) {
menu.Bind(wxEVT_MENU, [=](wxCommandEvent &ev){
if(ev.GetId() == idDel) {
auto f = (Frame*) GetParent();
auto f = globaldis;
CHi_DeleteKeyframe(f->graph->backendNG, kfs, kfIdx);
@@ -337,7 +341,7 @@ Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) {
}
void Timeline::Paint(wxPaintEvent &ev) {
auto frame = (Frame*) GetParent();
auto frame = globaldis;
wxPaintDC dc{this};
@@ -405,6 +409,10 @@ void Timeline::Paint(wxPaintEvent &ev) {
if(end == 0) {
end = gn->logical->ng->duration;
if(end == -1) {
// Hack :)
end = 3600 * 10;
}
}
start *= scale;