#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; }