diff options
author | Luke T. Shumaker <lukeshu@lukeshu.com> | 2025-02-20 22:10:05 -0700 |
---|---|---|
committer | Luke T. Shumaker <lukeshu@lukeshu.com> | 2025-06-19 23:28:09 -0600 |
commit | 119ebba54b0d4a8656b3966639d9263be370b5a7 (patch) | |
tree | 8db77cbb70f608abebe34284e1bb2a8367047c93 /vid-scratch/nutgen-sidedata.c | |
parent | 756eb1635ba61b2888efa1ae2a25acd40cb34d11 (diff) |
vid-scratch: Add consolidated versions of my experiments from 2025-02-XXlukeshu/vid-scratch
Diffstat (limited to 'vid-scratch/nutgen-sidedata.c')
-rw-r--r-- | vid-scratch/nutgen-sidedata.c | 252 |
1 files changed, 252 insertions, 0 deletions
diff --git a/vid-scratch/nutgen-sidedata.c b/vid-scratch/nutgen-sidedata.c new file mode 100644 index 0000000..62fc680 --- /dev/null +++ b/vid-scratch/nutgen-sidedata.c @@ -0,0 +1,252 @@ +/* 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); +} +size_t app_res_sm_size(enum app_res) { + /* See app_write_frame() */ + return 19; +} + +#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; +}; + +#define APP_COMMON_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. */ + + +bool app_write_intro(int fd, struct app_state *state, 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)); + + /* Changing resolution mid-stream requires per-fame side-data, + * which requires FLAG_SM_DATA, which requires NUT v4. + * + * Unfortunately, libnut / nututils does not support v4, so + * there goes a good debugging tool. + */ + +#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, 1); /*! stream_count */ + nut_append_vu(&pkt, /*! max_distance */ + APP_FRAME_MAX_HEADER + + app_res_sm_size(APP_RES_MAX) + + 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 (at `res`) */ + nut_append_vu(&pkt, APP_COMMON_FLAGS); /*! flags */ + 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, 0); /*! 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 (change to `res`) */ + nut_append_vu(&pkt, APP_COMMON_FLAGS| /*! flags */ + NUT_FRAMEFLAG_SM_DATA); + 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, 0); /*! 2: fields.stream */ + nut_append_vu(&pkt, /*! 3: fields.size_lsb */ + (app_res_sm_size(res) + 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=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 ******************************************************/ + pkt.len = 0; + + 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(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 */ + state->res = APP_RES_640_480; + nut_append_vu(&pkt, app_res_w(state->res)); /*! width */ + nut_append_vu(&pkt, app_res_h(state->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}; + + /* frame header ( 1+10+1+4 = 16 bytes) */ + uint64_t frame_beg = out.len; + nut_append_u8(&out, 1+(2*res)+(res != state->res ? 1 : 0)); /*! frame_code (1 byte) */ + nut_append_vu(&out, time_ns); /*! coded_pts (<=10 bytes) */ + nut_append_vu(&out, /*! data_size_msb (1 byte) */ + (app_res_fb_size(res) + (res != state->res ? app_res_sm_size(res) : 0)) / APP_FRAME_SIZE_MSB_MUL); + nut_append_u32(&out, nut_crc32(&out.dat[frame_beg], out.len - frame_beg)); /*! checksum (4 bytes) */ + assert(out.len <= APP_FRAME_MAX_HEADER); + + /* side/meta data ( 1+(6+2)+(7+2)+1 = 19 bytes) */ + if (res != state->res) { + uint64_t data_beg = out.len; + nut_append_vu(&out, 2); /*! side_data_count (1 byte) */ + nut_append_vb_str(&out, "Width"); /*! side_data[0].name (1+5 bytes) */ + nut_append_vu(&out, app_res_w(res)); /*! side_data[0].value (2 bytes) */ + nut_append_vb_str(&out, "Height"); /*! side_data[1].name (1+6 bytes) */ + nut_append_vu(&out, app_res_h(res)); /*! side_data[1].value (2 bytes) */ + nut_append_vu(&out, 0); /*! meta_data_count (1 byte) */ + assert(out.len - data_beg == app_res_sm_size(res)); + } + + /* 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() { + struct app_state state; + if (app_write_intro(1, &state, 0)) + return 1; + + 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%3))+x] = font[i][((y/SCALE)*8)+(x/SCALE)] == ' ' ? 0b11000000 : 0b00000011; + if (app_write_frame(1, &state, ((uint64_t)i)*1000000000ULL, i%3, framebuffer)) + return 1; + } + return 0; +} |