// C++ encapsulation for libfame,
// by Bram Stolk

#include <fame.h>


#define CLAMP(x) \
(unsigned char) ((x)<0?0:(((x)>255)?255:(x)))

class VideoEncoder
{
  public:

    VideoEncoder(char *fname, int width=176, int height=144) :
      w(width),
      h(height),
      context(0),
      buffer(0),
      f(0)
    {
      static fame_parameters_t parms=FAME_PARAMETERS_INITIALIZER;
      f=fopen(fname,"wb");
      assert(f);
      context = fame_open();
      assert(context);

      yuv.w = w;
      yuv.h = h;
      yuv.p = w;
      yuv.y = new unsigned char [w*h];
      yuv.u = new unsigned char [w*h/4];
      yuv.v = new unsigned char [w*h/4];

      int bufferlen = 1024*1024;
      buffer = new unsigned char [bufferlen];

#if 0
      fame_object_t *object = fame_get_object(context, "profile/mpeg4/simple");
      fame_register(context, "profile", object);

      object = fame_get_object(context, "motion/pmvfast");
      fame_register(context, "motion", object);
#endif

      parms.width = w;
      parms.height = h;
      parms.slices_per_frame=1;
      parms.frame_rate_num=20;
      parms.frame_rate_den=1;
      parms.quality = 91;
      parms.coding = "IPPPPP";
      parms.search_range=15;
#if 0
      parms.frames_per_sequence=1024;
      parms.shape_quality=50;
#endif
      parms.verbose=1;
      fame_init(context, &parms, buffer, bufferlen);
    }

    ~VideoEncoder()
    {
      int written = fame_close(context);
      fwrite(buffer, written, 1, f);
      fclose(f);
      delete [] buffer;
      delete [] yuv.y;
      delete [] yuv.u;
      delete [] yuv.v;
    }

    bool AddFrame(unsigned char *pixels)
    { 
      // cvt rgb to yuv
      for (int y=0; y<h; y+=2)
      {
        for (int x=0; x<w; x+=2)
        {
          unsigned char vy[4],vu[4],vv[4];

          rgb_to_yuv(pixels + 3*((y+0)*w + (x+0)), vy[0],vu[0],vv[0]);
          rgb_to_yuv(pixels + 3*((y+0)*w + (x+1)), vy[1],vu[1],vv[1]);
          rgb_to_yuv(pixels + 3*((y+1)*w + (x+0)), vy[2],vu[2],vv[2]);
          rgb_to_yuv(pixels + 3*((y+1)*w + (x+1)), vy[3],vu[3],vv[3]);
          // Y
          yuv.y[(y+0)*w+(x+0)] = vy[0];
          yuv.y[(y+0)*w+(x+1)] = vy[1];
          yuv.y[(y+1)*w+(x+0)] = vy[2];
          yuv.y[(y+1)*w+(x+1)] = vy[3];
          // Cb
          yuv.u[y*w/4+x/2] = (vu[0]+vu[1]+vu[2]+vu[3])/4;
          // Cr
          yuv.v[y*w/4+x/2] = (vv[0]+vv[1]+vv[2]+vv[3])/4;
        }
      }
      fame_start_frame(context, &yuv, 0);
      int written = fame_encode_slice(context);
      fwrite(buffer, written, 1, f);
      fame_end_frame(context,0);
      return true;
    }

    void rgb_to_yuv(unsigned char *rgb, unsigned char &y, unsigned char &u, unsigned char &v) const
    {
      y = CLAMP( 0.257 * rgb[0] + 0.504 * rgb[1] + 0.098 * rgb[2] + 16);
      v = CLAMP( 0.439 * rgb[0] - 0.368 * rgb[1] - 0.071 * rgb[2] + 128);
      u = CLAMP(-0.148 * rgb[0] - 0.291 * rgb[1] + 0.439 * rgb[2] + 128);
    }

  protected:
    int w,h;
    fame_context_t *context;
    unsigned char *buffer;
    fame_yuv_t yuv;
    FILE *f;
};

