/* ------------------------------------------------------------------------ * Creative YUV Video Encoder * * Dr. Tim Ferguson, 2001. * For more details on the algorithm: * http://www.csse.monash.edu.au/~timf/videocodec.html * * This is a very simple predictive coder. A video frame is coded in YUV411 * format. The first pixel of each scanline is coded using the upper four * bits of its absolute value. Subsequent pixels for the scanline are coded * using the difference between the last pixel and the current pixel (DPCM). * The DPCM values are coded using a 16 entry table found at the start of the * frame. Thus four bits per component are used and are as follows: * UY VY YY UY VY YY UY VY... * This code assumes the frame width will be a multiple of four pixels. This * should probably be fixed. * The code implements a non-linear quantisation of the prediction errors. * * You may freely use this source code. I only ask that you reference its * source in your projects documentation: * Tim Ferguson: http://www.csse.monash.edu.au/~timf/ * ------------------------------------------------------------------------ */ #include #include #include #include #include #include #define AVIIF_TWOCC 0x00000002L #define AVIIF_KEYFRAME 0x00000010L /* ------------------------------------------------------------------------ */ static inline int cyuv_gen_delta(int cur, int new) { int delta, v; delta = (new - cur); v = (int)rint(sqrt((double)abs(delta))); if(delta < 0) return(v >= 8 ? 15 : v + 8); else return(v > 8 ? 0 : 8 - v); } /* ------------------------------------------------------------------------ */ static inline void cyuv_rgb_to_411(unsigned char *frame, int *y0, int *y1, int *y2, int *y3, int *u, int *v) { float fu, fv; #define Y_VAL(r, g, b) (int)rint(((float)r * 0.299) + ((float)g * 0.587) + ((float)b * 0.114)) #define U_VAL(r, g, b) (((float)r * 0.5) + ((float)g * -0.4187) + ((float)b * -0.0813)) #define V_VAL(r, g, b) (((float)r * -0.1687) + ((float)g * -0.3313) + ((float)b * 0.5)) #define CLIPV(a, l, h) ((a) < l ? l : ((a) > h ? h : (a))) *y0 = Y_VAL(frame[0], frame[1], frame[2]); fu = U_VAL(frame[0], frame[1], frame[2]); fv = V_VAL(frame[0], frame[1], frame[2]); *y1 = Y_VAL(frame[3], frame[4], frame[5]); fu += U_VAL(frame[3], frame[4], frame[5]); fv += V_VAL(frame[3], frame[4], frame[5]); *y2 = Y_VAL(frame[6], frame[7], frame[8]); fu += U_VAL(frame[6], frame[7], frame[8]); fv += V_VAL(frame[6], frame[7], frame[8]); *y3 = Y_VAL(frame[9], frame[10], frame[11]); fu += U_VAL(frame[9], frame[10], frame[11]); fv += V_VAL(frame[9], frame[10], frame[11]); *y0 = CLIPV(*y0, 0, 255); *y1 = CLIPV(*y1, 0, 255); *y2 = CLIPV(*y2, 0, 255); *y3 = CLIPV(*y3, 0, 255); *u = (int)rint(CLIPV((fu/4 + 128), 0, 255)); *v = (int)rint(CLIPV((fv/4 + 128), 0, 255)); } /* ------------------------------------------------------------------------ * This function encodes an RGB frame into a buffer using CYUV. * * Input: * frame - the input frame buffer (24-bit RGB format) * width - the width of the input frame * height - the height of the input frame * Output: * buf - the encoded output buffer to be saved in an AVI file * size - the number of bytes put in the output buffer after encoding * flags - the AVI flags the frame should be coded with (for the index) */ void cyuv_encode(unsigned char *frame, int width, int height, unsigned char *buf, int *size, unsigned int *flags) { int xpos, ypos, i, cur_Y = 0, cur_U = 0, cur_V = 0, delta_Y1, delta_Y2, delta_U, delta_V, delta_y_tbl[16], delta_c_tbl[16], delta_arr[1024], bpos = 0, y0, y1, y2, y3, u, v; #define get_delta(cur, new) delta_arr[512 + (new) - (cur)]; #define app_delta(tbl, c, d) { c += tbl[d]; c = (c < 0 ? 0 : (c > 255 ? 255 : c)); } for(i = 0; i < 1024; i++) delta_arr[i] = cyuv_gen_delta(512, i); for(i = 0; i < 16; i++) { delta_y_tbl[i] = (i - 8) * (i - 8) * (i > 8 ? -1 : 1); delta_c_tbl[i] = delta_y_tbl[i]; } for(i = 0; i < 16; i++) buf[bpos++] = delta_y_tbl[i]; /* luma */ for(i = 0; i < 16; i++) buf[bpos++] = delta_y_tbl[i]; /* luma */ for(i = 0; i < 16; i++) buf[bpos++] = delta_c_tbl[i]; /* chroma */ for(ypos = 0; ypos < height; ypos++) for(xpos = 0; xpos < width; xpos += 4) { cyuv_rgb_to_411(frame, &y0, &y1, &y2, &y3, &u, &v); frame += (3 * 4); if(xpos == 0) { cur_Y = y0 & 0xf0; cur_U = u & 0xf0; cur_V = v & 0xf0; buf[bpos++] = (cur_Y >> 4) | cur_U; delta_Y1 = get_delta(cur_Y, y1); buf[bpos++] = (delta_Y1 & 0x0f) | cur_V; app_delta(delta_y_tbl, cur_Y, delta_Y1); } else { delta_Y1 = get_delta(cur_Y, y0); delta_U = get_delta(cur_U, u); buf[bpos++] = (delta_Y1 & 0x0f) | (delta_U << 4); app_delta(delta_y_tbl, cur_Y, delta_Y1); app_delta(delta_c_tbl, cur_U, delta_U); delta_Y1 = get_delta(cur_Y, y1); delta_V = get_delta(cur_V, v); buf[bpos++] = (delta_Y1 & 0x0f) | (delta_V << 4); app_delta(delta_y_tbl, cur_Y, delta_Y1); app_delta(delta_c_tbl, cur_V, delta_V); } delta_Y1 = get_delta(cur_Y, y2); app_delta(delta_y_tbl, cur_Y, delta_Y1); delta_Y2 = get_delta(cur_Y, y3); app_delta(delta_y_tbl, cur_Y, delta_Y2); buf[bpos++] = (delta_Y1 & 0x0f) | (delta_Y2 << 4); } *size = bpos; *flags = AVIIF_TWOCC | AVIIF_KEYFRAME; }