cuticle/hi/mkv.c

612 lines
18 KiB
C

#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>
#include<arpa/inet.h>
#include<sys/random.h>
#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;
}