From 50c9a7a6ffabf01de5a2373b0fbd1f15b9b01bd7 Mon Sep 17 00:00:00 2001 From: mid <> Date: Mon, 31 Mar 2025 21:37:04 +0300 Subject: [PATCH] Switch from WebM to full Matroska muxer --- Makefile | 5 +- hi/bs.h | 74 +++++++ hi/mkv.c | 611 +++++++++++++++++++++++++++++++++++++++++++++++++++ hi/node.c | 2 + hi/node.h | 2 + hi/opus.c | 38 +++- hi/vpxenc.c | 33 ++- ui/frame.cpp | 16 +- 8 files changed, 758 insertions(+), 23 deletions(-) create mode 100644 hi/mkv.c diff --git a/Makefile b/Makefile index e20c15b..ece78b5 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ all: $(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) + $(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) @@ -24,6 +24,7 @@ all: $(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) -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 mkv.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` diff --git a/hi/bs.h b/hi/bs.h index 70f1259..f580a00 100644 --- a/hi/bs.h +++ b/hi/bs.h @@ -5,6 +5,9 @@ extern "C" { #endif +#include +#include + #define CUTIHI_BS_FLAG_KEY 1 #define CUTIHI_BS_SETUP_PACKET 2 @@ -20,6 +23,77 @@ 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); + memset((uint8_t*) ret + oldsz, 0, newsz - oldsz); + return ret; +} + +typedef struct { + uint16_t width; + uint16_t height; +} CHiVPxBSSettings; + #ifdef __cplusplus } #endif diff --git a/hi/mkv.c b/hi/mkv.c new file mode 100644 index 0000000..7bfd23d --- /dev/null +++ b/hi/mkv.c @@ -0,0 +1,611 @@ +#include"node.h" + +#include +#include + +#include + +#include + +#include"mode.h" + +#include"img.h" +#include + +#include + +#include + +#include"minitrace.h" + +#include"linearity.h" + +#include"yuv.h" + +#include + +#include +#include + +#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 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].data.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].data.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->videoBacklog && this->videoBacklog->count && (this->videoBacklog->data[0].flags & CUTIHI_BS_FLAG_KEY)) || (nextTimestamp - this->currentClusterTimecode > 15000); + + if(shouldUpdateCluster) { + if(this->currentClusterTimecode != 0) { + ebml_writer_pop(&this->wr); + } + + // 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); + + size_t avccSz = 0; + uint8_t *avcc = annexb_to_avcc(frame->ptr, frame->sz, &avccSz); + + 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); + 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[0])->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[0])->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; + + int trackNum = 1; + + if(pubn->sinks[0].data.linked.to) { + this->videoBacklog = CHi_BS_Empty(); + this->videoTrack = trackNum++; + } + + if(pubn->sinks[1].data.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) { + 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; +} diff --git a/hi/node.c b/hi/node.c index 0bab149..fcac92e 100644 --- a/hi/node.c +++ b/hi/node.c @@ -1528,6 +1528,8 @@ CUTIVIS int CHi_NodeGraphLoad(CHiNodeGraph *ng, CHiLoadReader reader, void *ud) 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 = ng; diff --git a/hi/node.h b/hi/node.h index 2519648..0fb2145 100644 --- a/hi/node.h +++ b/hi/node.h @@ -275,6 +275,8 @@ CUTIVIS CHiPubNode *CHi_StreamRTMP(); CUTIVIS CHiPubNode *CHi_EncodeAAC(); +CUTIVIS CHiPubNode *CHi_MuxMatroska(); + CUTIVIS CHiValue *CHi_Crawl(CHiValue*); //CUTIVIS void CHi_Save(CHiNodeGraph *ng); diff --git a/hi/opus.c b/hi/opus.c index 0100a80..f1f5ec2 100644 --- a/hi/opus.c +++ b/hi/opus.c @@ -14,6 +14,8 @@ struct CHiEncodeOpusNode { size_t timestamp; + bool firstFrame; + OpusEncoder *enc; }; @@ -34,14 +36,38 @@ static int encodeopus_perform(CHiPubNode *pubn) { CHiBSFrames *frames = malloc(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; } diff --git a/hi/vpxenc.c b/hi/vpxenc.c index a3d60ba..677ebc6 100644 --- a/hi/vpxenc.c +++ b/hi/vpxenc.c @@ -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; @@ -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); } } diff --git a/ui/frame.cpp b/ui/frame.cpp index 8ae4c10..83eaa80 100644 --- a/ui/frame.cpp +++ b/ui/frame.cpp @@ -38,7 +38,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 +55,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); @@ -108,7 +109,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) { @@ -169,6 +170,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_SAVE}}; + gn->sources = {}; } gn->Fit(); @@ -922,7 +926,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 +995,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 +1034,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) {