summaryrefslogtreecommitdiff
path: root/vid-scratch/nutgen-gif.c
diff options
context:
space:
mode:
Diffstat (limited to 'vid-scratch/nutgen-gif.c')
-rw-r--r--vid-scratch/nutgen-gif.c289
1 files changed, 289 insertions, 0 deletions
diff --git a/vid-scratch/nutgen-gif.c b/vid-scratch/nutgen-gif.c
new file mode 100644
index 0000000..15c3b75
--- /dev/null
+++ b/vid-scratch/nutgen-gif.c
@@ -0,0 +1,289 @@
+/* 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 <fcntl.h> /* for open() */
+
+#include "common.h"
+
+/* High-level (specific to the properties we want) ****************************/
+
+enum app_res {
+ APP_RES_INVALID = -1,
+ APP_RES_640_480 = 0,
+ APP_RES_720_480 = 1,
+ APP_RES_720_576 = 2,
+ APP_RES_NUM = 3,
+};
+#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;
+ uint64_t len;
+ uint64_t last_syncpoint;
+};
+
+#define GIF_SCREENFLAG_GLOBAL_COLOR_TABLE (1<<7)
+#define GIF_SCREENFLAG_BITS_PER_COLOR(x) ({ static_assert((x) <= 8); (((x)-1)&0b111)<<4; })
+#define GIF_SCREENFLAG_GLOBAL_COLOR_TABLE_SORTED (1<<3)
+#define GIF_SCREENFLAG_GLOBAL_COLOR_TABLE_SIZE(x) ({ static_assert(__builtin_popcount(x) == 1 && __builtin_ctz(x) <= 8); __builtin_ctz(x)-1; })
+
+#define GIF_ASPECT_RATIO(w, h) (64*(w)/(h)+15)
+
+#define GIF_IMAGEFLAG_LOCAL_COLOR_TABLE (1<<7)
+#define GIF_IMAGEFLAG_INTERLACE (1<<6)
+#define GIF_IMAGEFLAG_LOCAL_COLOR_TABLE_SORTED (1<<5)
+#define _GIF_IMAGEFLAG_4 (1<<4)
+#define _GIF_IMAGEFLAG_3 (1<<3)
+#define GIF_IMAGEFLAG_LOCAL_COLOR_TABLE_SIZE(x) ({ static_assert(__builtin_popcount(x) == 1 && __builtin_ctz(x) <= 8); __builtin_ctz(x)-1; })
+
+#define UINT16LE(x) ((x)&0xFF), (((x)>>8)&0xFF)
+
+void app_append_intro(struct buf *out, enum app_res res) {
+ [[maybe_unused]] uint8_t intro[] = {
+ /* Header */
+ 'G', 'I', 'F', /*! Signature */
+ '8', '9', 'a', /*! Version */
+
+ /* Logical Screen Descriptor */
+ UINT16LE(app_res_w(res)), /*! Logical Screen Width */
+ UINT16LE(app_res_h(res)), /*! Logical Screen Height */
+ GIF_SCREENFLAG_GLOBAL_COLOR_TABLE| /*! Global Color Table Flag */
+ GIF_SCREENFLAG_BITS_PER_COLOR(8)| /*! Color Resolution */
+ 0| /*! Sort Flag */
+ GIF_SCREENFLAG_GLOBAL_COLOR_TABLE_SIZE(256), /*! Size of Global Color Table */
+ 0, /*! Background Color Index */
+ GIF_ASPECT_RATIO(1, 2), /*! Pixel Aspect Ratio */
+
+ /* Global Color Table */
+ /* identity-map R:G:B 3:3:2 bits */
+#define R3(idx) (((idx)>>5)&0b111)
+#define G3(idx) (((idx)>>2)&0b111)
+#define B2(idx) (((idx)>>0)&0b011)
+#define R8(idx) ( (R3(idx)<<5) | (R3(idx)<<2) | (R3(idx)>>1) )
+#define G8(idx) ( (G3(idx)<<5) | (G3(idx)<<2) | (G3(idx)>>1) )
+#define B8(idx) ( (B2(idx)<<6) | (B2(idx)<<4) | (B2(idx)<<2) | (B2(idx)<<0) )
+#define COLOR(idx) R8(idx), G8(idx), B8(idx)
+#define COLOR_2(base_idx) COLOR (base_idx), COLOR (base_idx+ 1)
+#define COLOR_4(base_idx) COLOR_2 (base_idx), COLOR_2 (base_idx+ 2)
+#define COLOR_8(base_idx) COLOR_4 (base_idx), COLOR_4 (base_idx+ 4)
+#define COLOR_16(base_idx) COLOR_8 (base_idx), COLOR_8 (base_idx+ 8)
+#define COLOR_32(base_idx) COLOR_16 (base_idx), COLOR_16 (base_idx+ 16)
+#define COLOR_64(base_idx) COLOR_32 (base_idx), COLOR_32 (base_idx+ 32)
+#define COLOR_128(base_idx) COLOR_64 (base_idx), COLOR_64 (base_idx+ 64)
+#define COLOR_256(base_idx) COLOR_128(base_idx), COLOR_128(base_idx+128)
+ COLOR_256(0),
+
+ /* Image Descriptor */
+ 0x2C, /*! Image Separator */
+ UINT16LE(0), /*! Image Left Position */
+ UINT16LE(0), /*! Image Top Position */
+ UINT16LE(app_res_w(res)), /*! Image Width */
+ UINT16LE(app_res_h(res)), /*! Image Height */
+ 0| /*! Local Color Table Flag */
+ 0, /*! Interlace Flag */
+
+
+ };
+ 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, 3); /*! version */
+ nut_append_vu(&pkt, 1); /*! stream_count */
+ nut_append_vu(&pkt, /*! max_distance */
+ APP_FRAME_MAX_HEADER +
+ app_res_fb_size(res));
+
+ /* 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();
+
+ /* frame_code=1 (res) */
+ 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, 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=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_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, 0); /*! msb_pts_shift */
+ 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);
+
+ /* flush **************************************************************/
+#undef BOGUS
+
+ free(pkt.dat);
+}
+
+bool app_write_frame(int fd, struct app_state *state, uint64_t time_ns, enum app_res res, void *framebuffer) {
+ assert(framebuffer);
+ assert(0 <= res && res < APP_RES_NUM);
+
+ struct buf out = {0};
+ struct buf pkt = {0};
+
+ if (res != state->res) {
+ app_append_intro(&out, res);
+ state->res = res;
+ }
+
+ /* syncpoint packet ***************************************************/
+ pkt.len = 0;
+ uint64_t syncpoint_beg = state->len + out.len;
+
+ nut_append_vu(&pkt, time_ns); /*! global_key_pts */
+ nut_append_vu(&pkt, state->last_syncpoint /*! back_ptr_div16 */
+ ? (syncpoint_beg - state->last_syncpoint)/16
+ : 0);
+
+ nut_append_packet(&out, nut_startcode_syncpoint, pkt.dat, pkt.len);
+
+ /* frame header ( 1+10+1+4 = 16 bytes) */
+ uint64_t frame_beg = out.len;
+ nut_append_u8(&out, 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;
+ state->len += out.len + app_res_fb_size(res);
+ state->last_syncpoint = syncpoint_beg;
+ return false;
+}
+
+/* Demo application ***********************************************************/
+
+int main() {
+ struct app_state state = {
+ .res = APP_RES_INVALID,
+ };
+
+ uint8_t framebuffer[app_res_fb_size(APP_RES_MAX)];
+
+#define SCALE 10
+ for (int i = 0; i < 10; i++) {
+ enum app_res res = (i/2)%APP_RES_NUM;
+ memset(framebuffer, 0b00011000, sizeof(framebuffer));
+ for (int y = 0; y < 4*SCALE; y++)
+ for (int x = 0; x < 8*SCALE; x++)
+ framebuffer[(y*app_res_w(res))+x] = font[i][((y/SCALE)*8)+(x/SCALE)] == ' ' ? 0b11000000 : 0b00000011;
+ if (i%2 == 0) {
+ char name[] = {'o', 'u', 't', '-', '0'+(i/2), '.', 'g', 'i', 'f', '\0'};
+ int fd = open(name, O_WRONLY|O_TRUNC|O_CREAT, 0666);
+ dup2(fd, 1);
+ close(fd);
+ state.len = 0;
+ state.last_syncpoint = 0;
+ }
+ if (app_write_frame(1, &state, ((uint64_t)i)*1000000000ULL, res, framebuffer))
+ return 1;
+ }
+ return 0;
+}