/* 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 "common.h" /* 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, }; #define APP_RES_MIN APP_RES_640_480 #define APP_RES_MAX APP_RES_720_576 size_t app_res_w(enum app_res res) { switch (res) { case APP_RES_640_480: return 640; case APP_RES_720_480: return 720; case APP_RES_720_576: return 720; default: assert_notreached("invalid res"); } } size_t app_res_h(enum app_res res) { switch (res) { case APP_RES_640_480: return 480/2; case APP_RES_720_480: return 480/2; case APP_RES_720_576: return 576/2; default: assert_notreached("invalid res"); } } size_t app_res_fb_size(enum app_res res) { return app_res_w(res)*app_res_h(res); } #define APP_FRAME_SIZE_MSB_MUL 0x2000 /* must be less than 0x4000 */ #define APP_FRAME_MAX_HEADER 16 struct app_state { enum app_res res; }; bool app_write_intro(int fd, uint64_t time_ns) { struct buf out = {0}; struct buf pkt = {0}; unsigned int frame_code = 0; #define INC_FRAME_CODE() do { frame_code++; if (frame_code == 'N') frame_code++; } while(0) append(&out, nut_file_id_string, sizeof(nut_file_id_string)); #define BOGUS(n) n /* main_header ********************************************************/ pkt.len = 0; /* head *******************************************/ nut_append_vu(&pkt, 4); /*! version */ nut_append_vu(&pkt, 0); /*! minor_version */ nut_append_vu(&pkt, APP_RES_MAX+1); /*! stream_count */ nut_append_vu(&pkt, /*! max_distance */ APP_FRAME_MAX_HEADER + app_res_fb_size(APP_RES_MAX)); /* 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" */ /* 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)); /*! 0: fields.pts */ nut_append_vu(&pkt, 1); /*! 1: fields.size_msb_nul */ INC_FRAME_CODE(); for (enum app_res res = APP_RES_MIN; res <= APP_RES_MAX; res++) { /* frame_code=(res*2)+1 (`res` keyframe) */ nut_append_vu(&pkt, /*! flags */ NUT_FRAMEFLAG_KEY | /* Because they're full framebuffers, all frames are keyframes. */ NUT_FRAMEFLAG_SIZE_MSB | /* 640*480/2 > 16384 (the max val of data_size_lsb). */ NUT_FRAMEFLAG_CODED_PTS | /* framerate is unknown, each frame must have a timestamp. */ NUT_FRAMEFLAG_CHECKSUM ); /* framerate is unknown, guard against exceeding max_pts_distance. */ nut_append_vu(&pkt, 6); /*! field_count */ nut_append_vu(&pkt, BOGUS(0)); /*! 0: fields.pts */ nut_append_vu(&pkt, APP_FRAME_SIZE_MSB_MUL); /*! 1: fields.size_msb_nul */ nut_append_vu(&pkt, res); /*! 2: fields.stream */ nut_append_vu(&pkt, /*! 3: fields.size_lsb */ app_res_fb_size(res) % APP_FRAME_SIZE_MSB_MUL); nut_append_vu(&pkt, 0); /*! 4: fields.reserved */ nut_append_vu(&pkt, 1); /*! 5: fields.count */ INC_FRAME_CODE(); /* frame_code=(res*2)+2 (`res` EOR frame) */ nut_append_vu(&pkt, /*! flags */ NUT_FRAMEFLAG_KEY | /* Because they're full framebuffers, all frames are keyframes. */ NUT_FRAMEFLAG_EOR | NUT_FRAMEFLAG_CODED_PTS | /* framerate is unknown, each frame must have a timestamp. */ NUT_FRAMEFLAG_CHECKSUM ); /* framerate is unknown, guard against exceeding max_pts_distance. */ nut_append_vu(&pkt, 6); /*! field_count */ nut_append_vu(&pkt, BOGUS(0)); /*! 0: fields.pts */ nut_append_vu(&pkt, BOGUS(1)); /*! 1: fields.size_msb_nul */ nut_append_vu(&pkt, res); /*! 2: fields.stream */ nut_append_vu(&pkt, 0); /*! 3: fields.size_lsb */ nut_append_vu(&pkt, 0); /*! 4: fields.reserved */ nut_append_vu(&pkt, 1); /*! 5: fields.count */ INC_FRAME_CODE(); } /* frame_code=N-255 (invalid) */ nut_append_vu(&pkt, NUT_FRAMEFLAG_INVALID); /*! flags */ nut_append_vu(&pkt, 2); /*! field_count */ nut_append_vu(&pkt, BOGUS(0)); /*! 0: fields.pts */ nut_append_vu(&pkt, /*! 1: fields.size_msb_nul */ 256-frame_code-(frame_code < 'N' ? 1 : 0)); /* tail *******************************************/ nut_append_vu(&pkt, 0); /*! header_count_minus_1 */ nut_append_vu(&pkt, NUT_MAINFLAG_PIPE_MODE); /*! main_flags */ nut_append_packet(&out, nut_startcode_main, pkt.dat, pkt.len); /* stream_header ******************************************************/ for (enum app_res res = APP_RES_MIN; res <= APP_RES_MAX; res++) { pkt.len = 0; nut_append_vu(&pkt, res); /*! 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(0)); /*! msb_pts_shift (only relevant if FRAMEFLAG_CODED_PTS) */ nut_append_vu(&pkt, BOGUS(0)); /*! max_pts_distance (all frames have a checksum) */ 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, app_res_w(res)); /*! width */ nut_append_vu(&pkt, app_res_h(res)); /*! 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); } /* syncpoint **********************************************************/ pkt.len = 0; nut_append_vu(&pkt, time_ns); /*! global_key_pts */ nut_append_vu(&pkt, 0); /*! back_ptr_div16 */ nut_append_packet(&out, nut_startcode_syncpoint, pkt.dat, pkt.len); /* flush **************************************************************/ #undef BOGUS bool ret = xwrite(fd, out.dat, out.len); free(out.dat); free(pkt.dat); return ret; } bool app_write_frame(int fd, struct app_state *state, uint64_t time_ns, enum app_res res, void *framebuffer) { assert(framebuffer); struct buf out = {0}; struct buf pkt = {0}; if (res != state->res) { /* EOR frame ( 1+10+4 = 15 bytes) */ uint64_t frame_beg = out.len; nut_append_u8(&out, (2*(state->res))+2); /*! frame_code (1 byte) */ nut_append_vu(&out, time_ns); /*! coded_pts (<=10 bytes) */ nut_append_u32(&out, nut_crc32(&out.dat[frame_beg], out.len - frame_beg)); /*! checksum (4 bytes) */ } /* frame header ( 1+10+1+4 = 16 bytes) */ uint64_t frame_beg = out.len; nut_append_u8(&out, (2*res)+1); /*! frame_code (1 byte) */ nut_append_vu(&out, time_ns); /*! coded_pts (<=10 bytes) */ nut_append_vu(&out, app_res_fb_size(res) / APP_FRAME_SIZE_MSB_MUL); /*! data_size_msb (1 byte) */ nut_append_u32(&out, nut_crc32(&out.dat[frame_beg], out.len - frame_beg)); /*! checksum (4 bytes) */ assert(out.len - frame_beg <= APP_FRAME_MAX_HEADER); /* flush */ bool err = xwrite(fd, out.dat, out.len); free(out.dat); free(pkt.dat); if (err) return true; /* frame data */ if (xwrite(fd, framebuffer, app_res_fb_size(res))) return true; /* return */ state->res = res; return false; } /* Demo application ***********************************************************/ int main() { if (app_write_intro(1, 0)) return 1; struct app_state state = { .res = 0, }; uint8_t framebuffer[app_res_fb_size(APP_RES_MAX)]; #define SCALE 10 for (int i = 0; i < 10; i++) { memset(framebuffer, 0, sizeof(framebuffer)); for (int y = 0; y < 4*SCALE; y++) for (int x = 0; x < 8*SCALE; x++) framebuffer[(y*app_res_w(i%(APP_RES_MAX+1)))+x] = font[i][((y/SCALE)*8)+(x/SCALE)] == ' ' ? 0b11000000 : 0b00000011; if (app_write_frame(1, &state, ((uint64_t)i)*1000000000ULL, i%(APP_RES_MAX+1), framebuffer)) return 1; } return 0; }