/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/***************************************************************************
 *            libfame_wrapper.cc
 *
 *  Sat Jul  2 11:11:31 CEST 2005
 *  Copyright  2005 Bent Bisballe
 *  deva@aasimon.org
 ****************************************************************************/

/*
 *  This file is part of MIaV.
 *
 *  MIaV is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  MIaV is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with MIaV; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
 */
#include <config.h>
#include "libfame_wrapper.h"

#include <errno.h>

#include "configuration.h"
#include "info.h"

#include "frame.h"

LibFAMEWrapper::LibFAMEWrapper()
{
  // FIXME: Hmmm... should this be detected somewhere?!
  int w = 720;
  int h = 576;

  // Initialize yuv structure.
  yuv.w = w;
  yuv.h = h;
  yuv.p = w;
  yuv.y = new unsigned char [w*h * 2];
  yuv.u = new unsigned char [w*h];// [w*h/4]
  yuv.v = new unsigned char [w*h];// [w*h/4]
  
  calc_bitrate = 0;
  frame_number = 0;

  ////////////LIBDV STUFF///////////////
  
  dvdecoder = NULL; // Initialize in encode method

  /////////LIBFAME STUFF///////////

  // Allocate the output buffer.
//  fame_buffer = new unsigned char [FAME_BUFFER_SIZE];

  // Open a new session of the fame library.
  // (If initialization was successful, it returns a non-null context which 
  // can then be used for subsequent library calls.)
  fame_context = fame_open();
  if(!fame_context) {
    MIaV::info->error("Unable to open FAME context, due to the following error: %s", strerror(errno));
    return;
  }

  /*
  typedef struct _fame_parameters_ {
    int width;                        // width of the video sequence
    int height;                       // height of the video sequence
    char const *coding;               // coding sequence
    int quality;                      // video quality
    int slices_per_frame;             // number of slices per frame
    unsigned int frames_per_sequence; // number of frames per sequence
    int frame_rate_num;               // numerator of frames per second
    int frame_rate_den;               // denominator of frames per second
    unsigned int shape_quality;       // binary shape quality
    unsigned int search_range;        // motion estimation search range
    unsigned char verbose;            // verbosity
  } fame_parameters_t;
  */
  // width and height specify the size of each frames of the video sequence. 
  // Both must be multiple of 16. width and height must be less than 4096x4096
  fame_par.width = 720;
  fame_par.height = 576;

  // coding is a string of I, P or B characters representing the sequence of 
  // frames the encoder must produce. I frames are intra-coded frames (similar 
  // to JPEG), whereas P and B frames are motion compressed, respectively 
  // predicted from past reference (I or P) frame, or bidirectionally predicted 
  // from past and future reference frame.
  std::string coding;
  if(MIaV::config->get("frame_sequence", &coding))
    MIaV::info->error("Could not read symbol [frame_sequence] from conf file!");
  fame_par.coding = coding.c_str();

  // quality is a percentage, which controls compression versus quality.
  int quality;
  if(MIaV::config->get("video_quality", &quality))
    MIaV::info->error("Could not read symbol [video_quality] from conf file!");
  fame_par.quality = quality;

  // Bitrate
  int bitrate;
  if(MIaV::config->get("video_bitrate", &bitrate))
    MIaV::info->error("Could not read symbol [video_bitrate] from conf file!");
  fame_par.bitrate = bitrate * 1000; // video bitrate in bytes pr second (0=VBR)

  // slices_per_frame is the number of frame slices per frame. More slices provide 
  // better error recovery. There must be at least one slice per frame, and at most 
  // height / 16
  fame_par.slices_per_frame = 1;//fame_par.height / 16;

  // frames_per_sequence is the maximum number of frames contained in a video 
  // sequence.
  fame_par.frames_per_sequence = 0xffffffff; // Unlimited length

  // frame_rate_num/frame_rate_den specify the number of frames per second for 
  // playback.
  fame_par.frame_rate_num = 25; // 25 / 1 fps = 25 fps
  fame_par.frame_rate_den = 1;

  // shape_quality is percentage determing the average binary shape accuracy in 
  // video with arbitrary shape.
  fame_par.shape_quality = 100; // Original shape

  // search_range specifies the motion estimation search range in pixel unit. 
  // Small search ranges work best with slow motion videos, whereas larger search 
  // ranges are rather for fast motion videos.
  fame_par.search_range = 0; // Adaptive search range

  // verbose when set to 1 outputs information on copyright, modules used and 
  // current frame on standard error.
  fame_par.verbose = 0;

  static const char profilename[] = "MIaV\0";
  fame_par.profile = profilename;              // profile name
  fame_par.total_frames = 0;        // total number of frames

  std::string codec;
  if(MIaV::config->get("encoding_codec", &codec))
    MIaV::info->error("Could not read symbol [encoding_codec] from conf file!");

  if(strcmp(codec.c_str(), "mpeg4") == 0) {

    MIaV::info->info("Using mpeg4 compression.");
    fame_object_t *object;
    
    object = fame_get_object(fame_context, "profile/mpeg4/simple");
    if(object) fame_register(fame_context, "profile", object);

  } else if(strcmp(codec.c_str(), "mpeg1") == 0) {

    MIaV::info->info("Using mpeg1 compression.");
    fame_object_t *object;
    
    object = fame_get_object(fame_context, "profile/mpeg1");
    if(object) fame_register(fame_context, "profile", object);

  } else if(strcmp(codec.c_str(), "mpeg1") == 0) {
  } else {
    MIaV::info->info("Using default (mpeg1) compression.");
  }

  fame_init(fame_context, &fame_par, fame_buffer, FAME_BUFFER_SIZE);
}

LibFAMEWrapper::~LibFAMEWrapper()
{
  delete [] yuv.y;
  delete [] yuv.u;
  delete [] yuv.v;
}

Frame *LibFAMEWrapper::encode(Frame *dvframe)
{
  //  if(!f) return; // The file was not opened.

  // Decode DV Frame to YUV422
  int w = 720;
  int h = 576;

  unsigned char *pixels[3];
  int pitches[3];

  if(!dvdecoder) {
    dvdecoder = dv_decoder_new(FALSE/*this value is unused*/, FALSE, FALSE);
    dvdecoder->quality = DV_QUALITY_BEST;

    dv_parse_header(dvdecoder, dvframe->data);
    //dv_parse_packs(decoder, frame->data); // Not needed anyway!
    
    dvdecoder->system = e_dv_system_625_50;  // PAL lines, PAL framerate
    dvdecoder->sampling = e_dv_sample_422;  // 4 bytes y, 2 bytes u, 2 bytes v
    dvdecoder->std = e_dv_std_iec_61834;
    dvdecoder->num_dif_seqs = 12;
  }

  pixels[ 0 ] = picture; // We use this as the output buffer
  pitches[ 0 ] = w * 2;
 
  dv_decode_full_frame(dvdecoder, 
                       dvframe->data, 
                       e_dv_color_yuv,
                       pixels,
                       pitches);

  // Convert YUV422 to YUV420p
  int w2 = w / 2;
  uint8_t *y = yuv.y;
  uint8_t *cb = yuv.u;
  uint8_t *cr = yuv.v;
  uint8_t *p = picture;
 
  for ( int i = 0; i < h; i += 2 ) {
    // process two scanlines (one from each field, interleaved)
    for ( int j = 0; j < w2; j++ ) {
      // packed YUV 422 is: Y[i] U[i] Y[i+1] V[i]
      *( y++ ) = *( p++ );
      *( cb++ ) = *( p++ );
      *( y++ ) = *( p++ );
      *( cr++ ) = *( p++ );
    }

    // process next two scanlines (one from each field, interleaved)
    for ( int j = 0; j < w2; j++ ) {
      // skip every second line for U and V
      *( y++ ) = *( p++ );
      p++;
      *( y++ ) = *( p++ );
      p++;
    }
  }

  // Allocate a new frame for the output
  Frame *output = new Frame(NULL, FAME_BUFFER_SIZE);

  // Init frame params
  dv_get_timestamp(dvdecoder, output->timestamp); // Set timecode
  output->size = 0;                              // Init size (incremented as we read)
  unsigned char* pt = output->data;              // Set pointer to start of data buffer

  // Encode YUV frame and write it to disk.
  fame_start_frame(fame_context, &yuv, 0);
  int written;
  
  while((written = fame_encode_slice(fame_context))) {
    memcpy(pt, fame_buffer, written);
    pt += written;
    output->size += written;
  }

  //  fame_frame_statistics_t stats;

  //  fame_end_frame(fame_context, &stats);
  /*
  MIaV::info->info("frame_number: %d, coding: %c, target_bits: %d, actual_bits: %d, spatial_activity: %d, quant_scale: %f",
             stats.frame_number,
             stats.coding,
             stats.target_bits,
             stats.actual_bits,
             stats.spatial_activity,
             stats.quant_scale);
  */
  /*
    fame_frame_statistics_t_ {
       unsigned int frame_number;
       char coding;
       signed int target_bits;
       unsigned int actual_bits;
       unsigned int spatial_activity;
       float quant_scale;
    }
  */
  frame_number++;
  calc_bitrate += output->size; //stats.actual_bits;
  output->bitrate = (unsigned int)((double)calc_bitrate / (double)frame_number) * 25;

  return output;
}