summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libmisc/include/libmisc/macro.h2
-rw-r--r--libmkv/nut-3.c498
2 files changed, 499 insertions, 1 deletions
diff --git a/libmisc/include/libmisc/macro.h b/libmisc/include/libmisc/macro.h
index d11b99f..fe61410 100644
--- a/libmisc/include/libmisc/macro.h
+++ b/libmisc/include/libmisc/macro.h
@@ -19,7 +19,7 @@
#define LM_ARRAY_LEN(ary) (sizeof(ary)/sizeof((ary)[0]))
#define LM_CEILDIV(n, d) ( ((n)+(d)-1) / (d) )
#define LM_ROUND_UP(n, d) ( LM_CEILDIV(n, d) * (d) ) /** Return `n` rounded up to the nearest multiple of `d` */
-#define LM_NEXT_POWER_OF_2(x) ((1ULL)<<((sizeof(unsigned long long)*8)-__builtin_clzll(x)))
+#define LM_NEXT_POWER_OF_2(x) ( (x) ? 1ULL<<((sizeof(unsigned long long)*8)-__builtin_clzll(x)) : 1)
/* strings */
diff --git a/libmkv/nut-3.c b/libmkv/nut-3.c
new file mode 100644
index 0000000..6a7ac4a
--- /dev/null
+++ b/libmkv/nut-3.c
@@ -0,0 +1,498 @@
+/* nut.c - A simple NUT encoder with the following properties:
+ *
+ * - Each frame is an uncompressed 8-bit RGB (specifically `(msb)2B
+ * 3G 3R(lsb)`, known as the 4CC "BGR\x08") framebuffer.
+ *
+ * - Each pixel in the framebuffer is non-square; it is twice as tall
+ * as it is wide.
+ *
+ * - The display-resolution of each frame may change between 640x480,
+ * 720x480, and 720x576. This is the display-resolution; because
+ * of non-square pixels, the framebuffer for each would be 640x240,
+ * 720x240, and 720x288, respectively.
+ *
+ * - VFR (Variable Frame Rate) - Each frame has its own timestamp.
+ */
+
+#include <assert.h> /* for static_assert() */
+#include <stdbool.h> /* for bool, true, false */
+#include <stdint.h> /* for uint{n}_t, UINT64_MAX */
+#include <stdio.h> /* for perror() */
+#include <stdlib.h> /* for realloc(), free() */
+#include <string.h> /* for memcpy(), memset() */
+#include <unistd.h> /* for write() */
+
+/* Generic ********************************************************************/
+
+#define LM_ARRAY_LEN(ary) (sizeof(ary)/sizeof((ary)[0]))
+#define LM_CEILDIV(n, d) ( ((n)+(d)-1) / (d) )
+#define LM_NEXT_POWER_OF_2(x) ( (x) ? 1ULL<<((sizeof(unsigned long long)*8)-__builtin_clzll(x)) : 1)
+
+struct buf {
+ uint8_t *dat;
+ size_t len;
+ size_t cap;
+};
+
+void append(struct buf *buf, uint8_t *dat, size_t len) {
+ assert(buf);
+ assert(len == 0 || dat);
+
+ if (buf->len + len > buf->cap) {
+ buf->cap = LM_NEXT_POWER_OF_2(buf->len + len);
+ buf->dat = realloc(buf->dat, buf->cap);
+ assert(buf->dat);
+ }
+ memcpy(&buf->dat[buf->len], dat, len);
+ buf->len += len;
+}
+
+void append_u32be(struct buf *buf, uint32_t val) {
+ assert(buf);
+
+ uint8_t dat[4] = {
+ (val>>(8*3))&0xFF,
+ (val>>(8*2))&0xFF,
+ (val>>(8*1))&0xFF,
+ (val>>(8*0))&0xFF,
+ };
+ append(buf, dat, sizeof(dat));
+}
+
+bool xwrite(int fd, uint8_t *dat, size_t len) {
+ assert(len == 0 || dat);
+
+ for (size_t done = 0; done < len;) {
+ ssize_t n = write(fd, &dat[done], len-done);
+ if (n < 0) {
+ perror("write");
+ return true;
+ }
+ done += n;
+ }
+ return false;
+}
+
+/* NUT ************************************************************************/
+
+/* Magic values. *****************************************/
+uint8_t nut_file_id_string[] = "nut/multimedia container"; /* including NUL terminator */
+uint8_t nut_startcode_main[] = {'N', 'M', 0x7A, 0x56, 0x1F, 0x5F, 0x04, 0xAD };
+uint8_t nut_startcode_stream[] = {'N', 'S', 0x11, 0x40, 0x5B, 0xF2, 0xF9, 0xDB };
+uint8_t nut_startcode_syncpoint[] = {'N', 'K', 0xE4, 0xAD, 0xEE, 0xCA, 0x45, 0x69 };
+uint8_t nut_startcode_index[] = {'N', 'X', 0xDD, 0x67, 0x2F, 0x23, 0xE6, 0x4E };
+uint8_t nut_startcode_info[] = {'N', 'I', 0xAB, 0x68, 0xB5, 0x96, 0xBA, 0x78 };
+
+#define NUT_FRAMEFLAG_KEY (1<< 0)
+#define NUT_FRAMEFLAG_EOR (1<< 1)
+#define NUT_FRAMEFLAG_CODED_PTS (1<< 3)
+#define NUT_FRAMEFLAG_STREAM_ID (1<< 4)
+#define NUT_FRAMEFLAG_SIZE_MSB (1<< 5)
+#define NUT_FRAMEFLAG_CHECKSUM (1<< 6)
+#define NUT_FRAMEFLAG_RESERVED (1<< 7)
+#define NUT_FRAMEFLAG_SM_DATA (1<< 8)
+#define NUT_FRAMEFLAG_HEADER_IDX (1<<10)
+#define NUT_FRAMEFLAG_MATCH_TIME (1<<11)
+#define NUT_FRAMEFLAG_CODED (1<<12)
+#define NUT_FRAMEFLAG_INVALID (1<<13)
+
+#define NUT_UNKNOWN_MATCH_TIME (1ULL-(1ULL<<62))
+
+enum nut_stream_class {
+ NUT_STREAMCLASS_VIDEO = 0,
+ NUT_STREAMCLASS_AUDIO = 1,
+ NUT_STREAMCLASS_SUBTITLES = 2,
+ NUT_STREAMCLASS_USERDATA = 3,
+};
+
+#define NUT_STREAMFLAG_FIXED_FPS (1<< 1)
+
+enum nut_colorspace {
+ NUT_COLORSPACE_UNKNOWN = 0,
+ NUT_COLORSPACE_ITU642_TRUNC = 1,
+ NUT_COLORSPACE_ITU709_TRUNC = 2,
+ NUT_COLORSPACE_ITU642_FULL = 17,
+ NUT_COLORSPACE_ITU709_FILL = 18,
+};
+
+/* Basic fixed-length types. *****************************/
+
+void nut_append_u8(struct buf *buf, uint8_t val) {
+ assert(buf);
+
+ append(buf, &val, 1);
+}
+
+void nut_append_u32(struct buf *buf, uint32_t val) {
+ assert(buf);
+
+ append_u32be(buf, val);
+}
+
+/* Basic variable-length types. **************************/
+
+void nut_append_vu(struct buf *buf, uint64_t val) {
+ assert(buf);
+
+ uint8_t dat[10] = {
+ 0x80|((val>>(7*9))&0x7F),
+ 0x80|((val>>(7*8))&0x7F),
+ 0x80|((val>>(7*7))&0x7F),
+ 0x80|((val>>(7*6))&0x7F),
+ 0x80|((val>>(7*5))&0x7F),
+ 0x80|((val>>(7*4))&0x7F),
+ 0x80|((val>>(7*3))&0x7F),
+ 0x80|((val>>(7*2))&0x7F),
+ 0x80|((val>>(7*1))&0x7F),
+ 0x00|((val>>(7*0))&0x7F),
+ };
+ uint8_t skip = 0;
+ while (dat[skip] == 0x80)
+ skip++;
+ append(buf, &dat[skip], sizeof(dat)-skip);
+}
+
+void nut_append_vs(struct buf *buf, int64_t val) {
+ assert(buf);
+ assert((val < 0 ? -val : val) <= UINT64_MAX>>1);
+
+ uint64_t temp;
+ if (val > 0)
+ temp = val << 1;
+ else
+ temp = ((-val) << 1) | 1;
+ temp--;
+ nut_append_vu(buf, temp);
+}
+
+void nut_append_vb(struct buf *buf, uint8_t *dat, size_t len) {
+ assert(buf);
+ assert(len == 0 || dat);
+
+ nut_append_vu(buf, len);
+ append(buf, dat, len);
+}
+
+#define nut_append_vb_str(buf, str) nut_append_vb(buf, str, sizeof(str)-1)
+
+/* Mid-level. ********************************************/
+
+uint32_t nut_crc32(uint8_t *dat, int len){
+ assert(len == 0 || dat);
+
+ /* "Generator polynomial is 0x104C11DB7." */
+ static const uint32_t table[16] = {
+ 0x00000000, 0x04C11DB7, 0x09823B6E, 0x0D4326D9,
+ 0x130476DC, 0x17C56B6B, 0x1A864DB2, 0x1E475005,
+ 0x2608EDB8, 0x22C9F00F, 0x2F8AD6D6, 0x2B4BCB61,
+ 0x350C9B64, 0x31CD86D3, 0x3C8EA00A, 0x384FBDBD,
+ };
+
+ /* "Starting value is zero." */
+ uint32_t crc = 0;
+
+ while (len--) {
+ crc ^= *dat++ << 24;
+ crc = (crc<<4) ^ table[crc>>28];
+ crc = (crc<<4) ^ table[crc>>28];
+ }
+ return crc;
+}
+
+void nut_append_packet(struct buf *buf, uint8_t *startcode, uint8_t *data, size_t len) {
+ assert(buf);
+ assert(startcode);
+ assert(len == 0 || data);
+
+ size_t packet_beg = buf->len;
+ append(buf, startcode, 8);
+ nut_append_vu(buf, len+4);
+ if (len+4 > 4096)
+ nut_append_u32(buf, nut_crc32(&buf->dat[packet_beg], buf->len - packet_beg));
+ size_t data_beg = buf->len;
+ append(buf, data, len);
+ nut_append_u32(buf, nut_crc32(&buf->dat[data_beg], buf->len - data_beg));
+}
+
+/* High-level (specific to the properties we want) ****************************/
+
+enum app_res {
+ APP_RES_640_480 = 0,
+ APP_RES_720_480 = 1,
+ APP_RES_720_576 = 2,
+};
+
+struct app_state {
+ uint64_t pts;
+ uint64_t len;
+ uint64_t last_syncpoint;
+};
+
+bool app_write_intro(int fd, struct app_state *state, uint64_t time_ns) {
+ assert(state);
+
+ struct buf out = {0};
+ struct buf pkt = {0};
+ struct buf hdr = {0};
+
+ append(&out, nut_file_id_string, sizeof(nut_file_id_string));
+
+ /* main_header ********************************************************/
+
+ nut_append_vu(&pkt, 3); /*! version */
+ nut_append_vu(&pkt, 1); /*! stream_count */
+ nut_append_vu(&pkt, (720*(576/2)) + 256/*TODO*/); /*! max_distance */
+
+ /* time bases *************************************/
+ nut_append_vu(&pkt, 1); /*! time_base_count */
+
+ /* time_base[0] = 1ns */
+ nut_append_vu(&pkt, 1); /*! numerator */
+ nut_append_vu(&pkt, 1000000000ULL); /*! denominator */
+
+ /* frame codes ************************************/
+ /* "A muxer SHOULD mark [frame codes] 0x00 and 0xFF as invalid" */
+#define BOGUS 0
+
+ /* frame_code=0 (invalid) */
+ nut_append_vu(&pkt, NUT_FRAMEFLAG_INVALID); /*! flags */
+ nut_append_vu(&pkt, 2); /*! field_count */
+ nut_append_vu(&pkt, BOGUS); /*! 0: fields.pts */
+ nut_append_vu(&pkt, 1); /*! 1: fields.mul */
+
+ /* frame_code=1 (640x480) */
+ nut_append_vu(&pkt, NUT_FRAMEFLAG_KEY|NUT_FRAMEFLAG_SIZE_MSB|NUT_FRAMEFLAG_SM_DATA); /*! flags */
+ nut_append_vu(&pkt, 8); /*! field_count */
+ nut_append_vu(&pkt, BOGUS); /*! 0: fields.pts */
+ nut_append_vu(&pkt, 0x2000); /*! 1: fields.mul */
+ nut_append_vu(&pkt, 0); /*! 2: fields.stream */
+ nut_append_vu(&pkt, (640*(480/2))&0x3FFF); /*! 3: fields.size_lsb */
+ nut_append_vu(&pkt, 0); /*! 4: fields.reserved */
+ nut_append_vu(&pkt, 1); /*! 5: fields.count */
+ nut_append_vs(&pkt, NUT_UNKNOWN_MATCH_TIME); /*! 6: fields.match */
+ nut_append_vu(&pkt, 1); /*! 7: fields.elision_header */
+
+ /* frame_code=2 (720x480) */
+ nut_append_vu(&pkt, NUT_FRAMEFLAG_KEY|NUT_FRAMEFLAG_SIZE_MSB|NUT_FRAMEFLAG_SM_DATA); /*! flags */
+ nut_append_vu(&pkt, 8); /*! field_count */
+ nut_append_vu(&pkt, BOGUS); /*! 0: fields.pts */
+ nut_append_vu(&pkt, 0x2000); /*! 1: fields.mul */
+ nut_append_vu(&pkt, 0); /*! 2: fields.stream */
+ nut_append_vu(&pkt, (720*(480/2))&0x3FFF); /*! 3: fields.size_lsb */
+ nut_append_vu(&pkt, 0); /*! 4: fields.reserved */
+ nut_append_vu(&pkt, 1); /*! 5: fields.count */
+ nut_append_vs(&pkt, NUT_UNKNOWN_MATCH_TIME); /*! 6: fields.match */
+ nut_append_vu(&pkt, 2); /*! 7: fields.elision_header */
+
+ /* frame_code=3 (720x576) */
+ nut_append_vu(&pkt, NUT_FRAMEFLAG_KEY|NUT_FRAMEFLAG_SIZE_MSB|NUT_FRAMEFLAG_SM_DATA); /*! flags */
+ nut_append_vu(&pkt, 8); /*! field_count */
+ nut_append_vu(&pkt, BOGUS); /*! 0: fields.pts */
+ nut_append_vu(&pkt, 0x2000); /*! 1: fields.mul */
+ nut_append_vu(&pkt, 0); /*! 2: fields.stream */
+ nut_append_vu(&pkt, (720*(576/2))&0x3FFF); /*! 3: fields.size_lsb */
+ nut_append_vu(&pkt, 0); /*! 4: fields.reserved */
+ nut_append_vu(&pkt, 1); /*! 5: fields.count */
+ nut_append_vs(&pkt, NUT_UNKNOWN_MATCH_TIME); /*! 6: fields.match */
+ nut_append_vu(&pkt, 2); /*! 7: fields.elision_header */
+
+ /* frame_code=4-255 (invalid) */
+ nut_append_vu(&pkt, NUT_FRAMEFLAG_INVALID); /*! flags */
+ nut_append_vu(&pkt, 2); /*! field_count */
+ nut_append_vu(&pkt, BOGUS); /*! 0: fields.pts */
+ nut_append_vu(&pkt, 256-4-1); /*! 1: fields.mul */
+
+ /* elision headers ********************************/
+ nut_append_vu(&pkt, 3); /*! header_count_minus_1 */
+
+ /* elision_header[1] (640x480) */
+ hdr.len = 0;
+ nut_append_vu(&hdr, 2); /*! side_data_count */
+ nut_append_vb_str(&hdr, "Width"); /*! side_data[0].name */
+ nut_append_vu(&hdr, 640); /*! side_data[0].value */
+ nut_append_vb_str(&hdr, "Height"); /*! side_data[1].name */
+ nut_append_vu(&hdr, 480/2); /*! side_data[1].value */
+ nut_append_vu(&hdr, 0); /*! meta_data_count */
+ nut_append_vb(&pkt, hdr.dat, hdr.len);
+
+ /* elision_header[2] (720x480) */
+ hdr.len = 0;
+ nut_append_vu(&hdr, 2); /*! side_data_count */
+ nut_append_vb_str(&hdr, "Width"); /*! side_data[0].name */
+ nut_append_vu(&hdr, 720); /*! side_data[0].value */
+ nut_append_vb_str(&hdr, "Height"); /*! side_data[1].name */
+ nut_append_vu(&hdr, 480/2); /*! side_data[1].value */
+ nut_append_vu(&hdr, 0); /*! meta_Data_count */
+ nut_append_vb(&pkt, hdr.dat, hdr.len);
+
+ /* elision_header[3] (720x576) */
+ hdr.len = 0;
+ nut_append_vu(&hdr, 2); /*! side_data_count */
+ nut_append_vb_str(&hdr, "Width"); /*! side_data[0].name */
+ nut_append_vu(&hdr, 720); /*! side_data[0].value */
+ nut_append_vb_str(&hdr, "Height"); /*! side_data[1].name */
+ nut_append_vu(&hdr, 576/2); /*! side_data[1].value */
+ nut_append_vu(&hdr, 0); /*! meta_data_count */
+ nut_append_vb(&pkt, hdr.dat, hdr.len);
+
+ nut_append_packet(&out, nut_startcode_main, pkt.dat, pkt.len);
+#undef BOGUS
+
+ /* stream_header ******************************************************/
+
+ pkt.len = 0;
+#define BOGUS 1
+ nut_append_vu(&pkt, 0); /*! stream_id */
+ nut_append_vu(&pkt, NUT_STREAMCLASS_VIDEO); /*! stream_class */
+ nut_append_vb(&pkt, "BGR\x08", 4); /*! fourcc */
+ nut_append_vu(&pkt, 0); /*! time_base_id */
+ nut_append_vu(&pkt, BOGUS); /*! msb_pts_shift (only relevant if FRAMEFLAG_CODED_PTS) */
+ nut_append_vu(&pkt, BOGUS); /*! max_pts_distance (only relevant if FRAMEFLAG_CODED_PTS) */
+ nut_append_vu(&pkt, 0); /*! decode_delay */
+ nut_append_vu(&pkt, 0); /*! stream_flags */
+ nut_append_vb(&pkt, NULL, 0); /*! codec_specific_data */
+ nut_append_vu(&pkt, BOGUS); /*! width (all frames set width) */
+ nut_append_vu(&pkt, BOGUS); /*! height (all frames set height) */
+ nut_append_vu(&pkt, 1); /*! sample_width */
+ nut_append_vu(&pkt, 2); /*! sample_height */
+ nut_append_vu(&pkt, NUT_COLORSPACE_UNKNOWN); /*! colorspace_type */
+
+ nut_append_packet(&out, nut_startcode_stream, pkt.dat, pkt.len);
+#undef BOGUS
+
+ /* flush **************************************************************/
+
+ bool ret = xwrite(fd, out.dat, out.len);
+ free(out.dat);
+ free(pkt.dat);
+ state->pts = time_ns;
+ state->len = out.len;
+ state->last_syncpoint = 0;
+ return ret;
+}
+
+bool app_write_frame(int fd, struct app_state *state, uint64_t time_ns, enum app_res res, void *framebuffer) {
+ assert(state);
+ assert(framebuffer);
+
+ struct buf out = {0};
+ struct buf pkt = {0};
+ uint64_t beg = state->len;
+
+ size_t framebuffer_size;
+ switch (res) {
+ case APP_RES_640_480: framebuffer_size = 640*(480/2); break;
+ case APP_RES_720_480: framebuffer_size = 720*(480/2); break;
+ case APP_RES_720_576: framebuffer_size = 720*(576/2); break;
+ }
+
+ /* packet (syncpoint) */
+ nut_append_vu(&pkt, time_ns); /*! global_key_pts */
+ nut_append_vu(&pkt, state->last_syncpoint ? (beg - state->last_syncpoint)/16 : 0); /*! back_ptr_div16 */
+ nut_append_packet(&out, nut_startcode_syncpoint, pkt.dat, pkt.len);
+
+ /* frame header */
+ nut_append_u8(&out, 1+res); /*! frame_code */
+ nut_append_vu(&out, (framebuffer_size>>13) & ~UINT64_C(1)); /*! data_size_msb */
+ /* side_data and meta_data come from elision headers */
+
+ /* flush */
+ bool err = xwrite(fd, out.dat, out.len);
+ free(out.dat);
+ free(pkt.dat);
+ if (err)
+ return true;
+ state->len += out.len;
+
+ /* frame data */
+ if (xwrite(fd, framebuffer, framebuffer_size))
+ return true;
+ state->len += framebuffer_size;
+
+ /* return */
+ state->pts = time_ns;
+ state->last_syncpoint = beg;
+ return false;
+}
+
+/* Demo application ***********************************************************/
+
+char font[10][8*4] = {
+ " ## "
+ " # # "
+ " # # "
+ " ## ",
+
+ " # "
+ " ## "
+ " # "
+ " ### ",
+
+ " ## "
+ " # # "
+ " # "
+ " #### ",
+
+ " ## "
+ " # "
+ " # "
+ " ## ",
+
+ " ## "
+ " # # "
+ " #### "
+ " # ",
+
+ " #### "
+ " ### "
+ " # "
+ " ### ",
+
+ " # "
+ " ### "
+ " # # "
+ " ## ",
+
+ " #### "
+ " # "
+ " # "
+ " # ",
+
+ " ## "
+ " # # "
+ " #### "
+ " ## ",
+
+ " ## "
+ " # # "
+ " ### "
+ " # ",
+};
+static_assert(LM_ARRAY_LEN(font) == 10);
+static_assert(sizeof(font[0]) == 32);
+
+int main() {
+ struct app_state state;
+ if (app_write_intro(1, &state, 0))
+ return 1;
+
+ uint8_t framebuffer[720*(576/2)];
+
+#define MUL 10
+ for (int i = 0; i < 10; i++) {
+ memset(framebuffer, 0, sizeof(framebuffer));
+ for (int y = 0; y < 4*MUL; y++) {
+ int width;
+ switch (i%3) {
+ case APP_RES_640_480: width = 640; break;
+ case APP_RES_720_480: width = 720; break;
+ case APP_RES_720_576: width = 720; break;
+ }
+ for (int x = 0; x < 8*MUL; x++)
+ framebuffer[(y*width)+x] = font[i][((y/MUL)*8)+(x/MUL)] == ' ' ? 0xFF : 0x00;
+ }
+ if (app_write_frame(1, &state, ((uint64_t)i)*((uint64_t)i)*1000000000ULL, i%3, framebuffer))
+ return 1;
+ }
+ return 0;
+}