/*
 *   PiDiP module.
 *   Copyright (c) by Yves Degoyon <ydegoyon@free.fr>
 *
 *   This program 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.
 *
 *   This program 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 this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

/*  This object is a theora file encoder object
 *  It uses libtheora and some of it code samples ( copyright xiph.org )
 *  Copyleft by Yves Degoyon ( ydegoyon@free.fr )
 *
 */


#include "pdp.h"
#include <math.h>
#include <time.h>
#include <sys/time.h>
#include <theora/theora.h>
#include <vorbis/codec.h>
#include <vorbis/vorbisenc.h>

#define DEFAULT_FRAME_RATE 25
#define MIN_VIDEO_QUALITY 0
#define MAX_VIDEO_QUALITY 63
#define DEFAULT_VIDEO_QUALITY 16
#define MIN_VIDEO_BITRATE 45
#define MAX_VIDEO_BITRATE 2000
#define DEFAULT_VIDEO_BITRATE 96
#define MIN_AUDIO_QUALITY -0.1
#define MAX_AUDIO_QUALITY 1.0
#define DEFAULT_AUDIO_QUALITY 0.5
#define MIN_AUDIO_BITRATE 8
#define MAX_AUDIO_BITRATE 2000
#define DEFAULT_AUDIO_BITRATE 32

#define DEFAULT_CHANNELS 2
#define DEFAULT_BITS 8
#define MAX_AUDIO_PACKET_SIZE (128 * 1024)
// streams hard-coded serial numbers
#define STREAMV_SNO 0x987654
#define STREAMA_SNO 0x456789

#ifndef _REENTRANT
# define _REENTRANT
#endif

static char   *pdp_theorout_version = "pdp_theorout~: version 0.1, a theora video/audio recording object, written by ydegoyon@free.fr";

typedef struct pdp_theorout_struct
{
    t_object x_obj;
    t_float x_f;

    t_outlet *x_outlet0;
    t_int x_packet0;
    t_int x_packet1;
    t_int x_dropped;
    t_int x_queue_id;

    t_int x_vwidth;
    t_int x_tvwidth;       /* theora 16 pixels aligned width value */
    t_int x_vheight;
    t_int x_tvheight;      /* theora 16 pixels aligned height value */
    t_int x_vsize;

    FILE  *x_tfile;
    t_int x_framerate;
    t_int x_newfile;
    t_int x_einit;
    t_int x_recflag;
    t_int x_enduprec;;
    t_int x_frameswritten;
    t_int x_frames;
    struct timeval x_tstart;
    struct timeval x_tzero;
    struct timeval x_tcurrent;
    struct timeval x_tlastrec;

     /* vorbis/theora structures */
    ogg_page         x_ogg_page;       // ogg page
    ogg_packet       x_ogg_packet;     // ogg packet
    ogg_stream_state x_statev;         // vorbis stream state
    ogg_stream_state x_statet;         // theora stream state
    theora_info      x_theora_info;    // theora info
    theora_comment   x_theora_comment; // theora comment
    theora_state     x_theora_state;   // theora state
    vorbis_info      x_vorbis_info;    // vorbis info
    vorbis_dsp_state x_dsp_state;      // vorbis dsp state
    vorbis_block     x_vorbis_block;   // vorbis block
    vorbis_comment   x_vorbis_comment; // vorbis comment
    yuv_buffer       x_yuvbuffer;      // yuv buffer

    t_int            x_akbps;          // audio bit rate
    t_int            x_vkbps;          // video bit rate
    t_float          x_aquality;       // audio quality
    t_int            x_vquality;       // video quality
    t_int            x_abytesout;      // audio bytes written
    t_int            x_vbytesout;      // video bytes written

     /* audio structures */
    t_float **x_audio_buf; /* buffer for incoming audio */
    t_int x_audioin_position; // writing position for incoming audio
    t_int x_channels;      // audio channels 
    t_int x_samplerate;    // audio sample rate 
    t_int x_bits;          // audio bits

} t_pdp_theorout;

    /* allocate internal ressources */
static void pdp_theorout_allocate(t_pdp_theorout *x)
{
  int ret;

    x->x_yuvbuffer.y_width=x->x_vwidth;
    x->x_yuvbuffer.y_height=x->x_vheight;
    x->x_yuvbuffer.y_stride=x->x_vwidth;

    x->x_yuvbuffer.uv_width=x->x_vwidth>>1;
    x->x_yuvbuffer.uv_height=x->x_vheight>>1;
    x->x_yuvbuffer.uv_stride=x->x_vwidth>>1;

    x->x_yuvbuffer.y = (char *)malloc( x->x_yuvbuffer.y_width * x->x_yuvbuffer.y_height );
    x->x_yuvbuffer.u = (char *)malloc( x->x_yuvbuffer.uv_width * x->x_yuvbuffer.uv_height );
    x->x_yuvbuffer.v = (char *)malloc( x->x_yuvbuffer.uv_width * x->x_yuvbuffer.uv_height );
}

    /* free internal ressources */
static void pdp_theorout_free_ressources(t_pdp_theorout *x)
{
    if ( x->x_yuvbuffer.y ) free( x->x_yuvbuffer.y );
    if ( x->x_yuvbuffer.u ) free( x->x_yuvbuffer.u );
    if ( x->x_yuvbuffer.v ) free( x->x_yuvbuffer.v );
}

    /* initialize the encoder */
static void pdp_theorout_init_encoder(t_pdp_theorout *x)
{
  t_int ret;

    x->x_einit=0;

    // init streams
    ogg_stream_init(&x->x_statet, STREAMA_SNO);
    ogg_stream_init(&x->x_statev, STREAMV_SNO);

    theora_info_init(&x->x_theora_info);
    x->x_theora_info.width=x->x_tvwidth;
    x->x_theora_info.height=x->x_tvheight;
    x->x_theora_info.frame_width=x->x_vwidth;
    x->x_theora_info.frame_height=x->x_vheight;
    x->x_theora_info.offset_x=(x->x_tvwidth-x->x_vwidth)>>1;
    x->x_theora_info.offset_y=(x->x_tvheight-x->x_vheight)>>1;
    x->x_theora_info.fps_numerator=x->x_framerate;
    x->x_theora_info.fps_denominator=1;
    x->x_theora_info.aspect_numerator=1;
    x->x_theora_info.aspect_denominator=1;
    x->x_theora_info.colorspace=OC_CS_UNSPECIFIED;
    x->x_theora_info.target_bitrate=x->x_vkbps;
    x->x_theora_info.quality=x->x_vquality;

    x->x_theora_info.dropframes_p=0;
    x->x_theora_info.quick_p=1;
    x->x_theora_info.keyframe_auto_p=1;
    x->x_theora_info.keyframe_frequency=64;
    x->x_theora_info.keyframe_frequency_force=64;
    x->x_theora_info.keyframe_data_target_bitrate=x->x_vkbps*1.5;
    x->x_theora_info.keyframe_auto_threshold=80;
    x->x_theora_info.keyframe_mindistance=8;
    x->x_theora_info.noise_sensitivity=1; 
    x->x_theora_info.sharpness=2; 

    theora_encode_init(&x->x_theora_state,&x->x_theora_info);
    theora_info_clear (&x->x_theora_info);

    vorbis_info_init(&x->x_vorbis_info);

    if(x->x_aquality > -0.1)
    {
      ret = vorbis_encode_init_vbr(&x->x_vorbis_info, x->x_channels, x->x_samplerate, x->x_aquality);
    }
    else
    {
      ret = vorbis_encode_init(&x->x_vorbis_info, x->x_channels, x->x_samplerate, -1, x->x_akbps, -1);
    }

    if (ret)
    {
      post( "pdp_theorout~ : could not initialize vorbis encoder" );
      x->x_einit=0;
      return;
    }

    vorbis_comment_init(&x->x_vorbis_comment);
    vorbis_comment_add_tag (&x->x_vorbis_comment, "ENCODER", "pdp_theorout~");
    vorbis_analysis_init(&x->x_dsp_state,&x->x_vorbis_info);
    vorbis_block_init(&x->x_dsp_state,&x->x_vorbis_block);
    
    post( "pdp_theorout~ : encoder initialized." );
    x->x_einit=1;

}

static void pdp_theorout_write_headers(t_pdp_theorout *x)
{
  t_int ret;
  ogg_packet aheader, aheadercomm, aheadercode;

    if ( !x->x_einit )
    {
      post( "pdp_theorout~ : trying to write headers but encoder is not initialized." );
      return;
    }

    if ( x->x_tfile == NULL )
    {
      post( "pdp_theorout~ : trying to write headers but no file is opened." );
      return;
    }

    theora_encode_header(&x->x_theora_state, &x->x_ogg_packet);
    ogg_stream_packetin(&x->x_statet, &x->x_ogg_packet);
    if(ogg_stream_pageout(&x->x_statet, &x->x_ogg_page)!=1)
    {
      post( "pdp_theorout~ : ogg encoding error." );
      return;
    }
    if ( ( ret = fwrite(x->x_ogg_page.header, 1, x->x_ogg_page.header_len, x->x_tfile) ) <= 0 )
    {
      post( "pdp_theorout~ : could not write headers (ret=%d).", ret );
      perror( "fwrite" );
      return;
    }
    if ( ( ret = fwrite(x->x_ogg_page.body, 1, x->x_ogg_page.body_len, x->x_tfile) ) <= 0 )
    {
      post( "pdp_theorout~ : could not write headers (ret=%d).", ret );
      perror( "fwrite" );
      return;
    }

    theora_comment_init(&x->x_theora_comment);
    theora_comment_add_tag (&x->x_theora_comment, "ENCODER", "pdp_theorout~");
    theora_encode_comment(&x->x_theora_comment, &x->x_ogg_packet);
    ogg_stream_packetin(&x->x_statet, &x->x_ogg_packet);
    theora_encode_tables(&x->x_theora_state, &x->x_ogg_packet);
    ogg_stream_packetin(&x->x_statet, &x->x_ogg_packet);

    vorbis_analysis_headerout(&x->x_dsp_state, &x->x_vorbis_comment, 
                              &aheader,&aheadercomm,&aheadercode);
    ogg_stream_packetin(&x->x_statev,&aheader); 

    if(ogg_stream_pageout(&x->x_statev, &x->x_ogg_page)!=1)
    {
      post( "pdp_theorout~ : ogg encoding error." );
      return;
    }
    if ( ( ret = fwrite(x->x_ogg_page.header, 1, x->x_ogg_page.header_len, x->x_tfile) ) <= 0 )
    {
      post( "pdp_theorout~ : could not write headers (ret=%d).", ret );
      perror( "fwrite" );
      return;
    }
    if ( ( ret = fwrite(x->x_ogg_page.body, 1, x->x_ogg_page.body_len, x->x_tfile) ) <= 0 )
    {
      post( "pdp_theorout~ : could not write headers (ret=%d).", ret );
      perror( "fwrite" );
      return;
    }

    // remaining vorbis header packets 
    ogg_stream_packetin(&x->x_statev, &aheadercomm);
    ogg_stream_packetin(&x->x_statev, &aheadercode);

    // flush all the headers
    while(1)
    {
      ret = ogg_stream_flush(&x->x_statet, &x->x_ogg_page);
      if(ret<0){
        post( "pdp_theorout~ : ogg encoding error." );
        return;
      }
      if(ret==0)break;
      if ( ( ret = fwrite(x->x_ogg_page.header, 1, x->x_ogg_page.header_len, x->x_tfile) ) <= 0 )
      {
        post( "pdp_theorout~ : could not write headers (ret=%d).", ret );
        perror( "fwrite" );
        return;
      }
      if ( ( ret = fwrite(x->x_ogg_page.body, 1, x->x_ogg_page.body_len, x->x_tfile) ) <= 0 )
      {
        post( "pdp_theorout~ : could not write headers (ret=%d).", ret );
        perror( "fwrite" );
        return;
      }
    }

    while(1)
    {
      ret = ogg_stream_flush(&x->x_statev, &x->x_ogg_page);
      if(ret<0){
        post( "pdp_theorout~ : ogg encoding error." );
        return;
      }
      if(ret==0)break;
      if ( ( ret = fwrite(x->x_ogg_page.header, 1, x->x_ogg_page.header_len, x->x_tfile) ) <= 0 )
      {
        post( "pdp_theorout~ : could not write headers (ret=%d).", ret );
        perror( "fwrite" );
        return;
      }
      if ( ( ret = fwrite(x->x_ogg_page.body, 1, x->x_ogg_page.body_len, x->x_tfile) ) <= 0 )
      {
        post( "pdp_theorout~ : could not write headers (ret=%d).", ret );
        perror( "fwrite" );
        return;
      }
    }
}

    /* terminate the encoding process */
static void pdp_theorout_shutdown_encoder(t_pdp_theorout *x)
{
    ogg_stream_clear(&x->x_statev);
    vorbis_block_clear(&x->x_vorbis_block);
    vorbis_dsp_clear(&x->x_dsp_state);
    vorbis_comment_clear(&x->x_vorbis_comment);
    vorbis_info_clear(&x->x_vorbis_info);
    ogg_stream_clear(&x->x_statet);
    theora_clear(&x->x_theora_state);
}


    /* close a video file */
static void pdp_theorout_close(t_pdp_theorout *x)
{
  int ret;

    if ( x->x_tfile ) 
    {
       if ( fclose( x->x_tfile ) < 0 )
       {
          post( "pdp_theorout~ : could not close output file" );
          perror( "fclose" );
       }
       x->x_tfile = NULL;   
    }
}

    /* open a new video file */
static void pdp_theorout_open(t_pdp_theorout *x, t_symbol *sfile)
{
  t_int ret=0;

    // close previous video file if existing
    pdp_theorout_close(x);

    if ( x->x_recflag ) {
      x->x_recflag = 0;
    }
    x->x_frameswritten = 0;

    if ( ( x->x_tfile = fopen( sfile->s_name, "w+" ) ) == NULL )
    {
      post( "pdp_theorout~ : could not open output file" );
      perror( "fopen" );
      return;
    }
    else
    {
      post( "pdp_theorout~ : opened >%s<", sfile->s_name);
    }
    x->x_newfile = 1;

}

   /* start recording */
static void pdp_theorout_start(t_pdp_theorout *x)
{
    if ( !x->x_tfile ) 
    {
       post("pdp_theorout~ : start received but no file has been opened ... ignored.");
       return;
    }

    if ( x->x_recflag == 1 ) 
    {
       post("pdp_theorout~ : start received but recording is started ... ignored.");
       return;
    }

    if ( gettimeofday(&x->x_tstart, NULL) == -1)
    {
       post("pdp_theorout~ : could not set start time" );
    }

    x->x_recflag = 1;
    pdp_theorout_init_encoder( x );
    pdp_theorout_write_headers( x );
    post("pdp_theorout~ : start recording at %d frames/second", x->x_framerate);
}

   /* stop recording */
static void pdp_theorout_stop(t_pdp_theorout *x)
{
    if ( !x->x_tfile ) 
    {
       post("pdp_theorout~ : stop received but no file has been opened ... ignored.");
       return;
    }

    if ( x->x_recflag == 0 ) 
    {
       post("pdp_theorout~ : stop received but recording is stopped ... ignored.");
       return;
    }

    x->x_recflag = 0;

    // record last packet
    x->x_enduprec = 1;

}

   /* set video bitrate */
static void pdp_theorout_vbitrate(t_pdp_theorout *x, t_floatarg vbitrate )
{
  if ( ( (t_int) vbitrate < MIN_VIDEO_BITRATE ) || ( (t_int) vbitrate > MAX_VIDEO_BITRATE ) )
  {
     post( "pdp_theorout~ : wrong video bitrate %d : should be in [%d,%d] kbps", 
                            (t_int) vbitrate, MIN_VIDEO_BITRATE, MAX_VIDEO_BITRATE );
     return;
  }
  x->x_vkbps = (t_int) vbitrate;
}

   /* set audio bitrate */
static void pdp_theorout_abitrate(t_pdp_theorout *x, t_floatarg abitrate )
{
  if ( ( (t_int) abitrate < MIN_AUDIO_BITRATE ) || ( (t_int) abitrate > MAX_AUDIO_BITRATE ) )
  {
     post( "pdp_theorout~ : wrong audio bitrate %d : should be in [%d,%d] kbps", 
                            (t_int) abitrate, MIN_AUDIO_BITRATE, MAX_AUDIO_BITRATE );
     return;
  }
  x->x_akbps = (t_int) abitrate;
}

   /* set video quality */
static void pdp_theorout_vquality(t_pdp_theorout *x, t_floatarg vquality )
{
  if ( ( (t_int) vquality < MIN_VIDEO_QUALITY ) || ( (t_int) vquality > MAX_VIDEO_QUALITY ) )
  {
     post( "pdp_theorout~ : wrong video quality %d : should be in [%d,%d]", 
                            (t_int) vquality, MIN_VIDEO_QUALITY, MAX_VIDEO_QUALITY );
     return;
  }
  x->x_vquality = (t_int) vquality;
}

   /* set audio quality */
static void pdp_theorout_aquality(t_pdp_theorout *x, t_floatarg aquality )
{
  if ( ( (t_int) aquality < MIN_AUDIO_QUALITY ) || ( (t_int) aquality > MAX_AUDIO_QUALITY ) )
  {
     post( "pdp_theorout~ : wrong audio quality %d : should be in [%d,%d]", 
                            (t_int) aquality, MIN_AUDIO_QUALITY, MAX_AUDIO_QUALITY );
     return;
  }
  x->x_aquality = (t_int) aquality;
}

    /* store audio data in PCM format in a buffer for now */
static t_int *pdp_theorout_perform(t_int *w)
{
  t_float *in1   = (t_float *)(w[1]);       // left audio inlet
  t_float *in2   = (t_float *)(w[2]);       // right audio inlet
  t_pdp_theorout *x = (t_pdp_theorout *)(w[3]);
  int n = (int)(w[4]);                      // number of samples
  t_float fsample;
  t_int   isample, i;

   if ( x->x_recflag ) 
   {
    // just fills the buffer
    while (n--)
    {
       fsample=*(in1++);
       if (fsample > 1.0) { fsample = 1.0; }
       if (fsample < -1.0) { fsample = -1.0; }
       x->x_audio_buf[0][x->x_audioin_position]=fsample;
       fsample=*(in2++);
       if (fsample > 1.0) { fsample = 1.0; }
       if (fsample < -1.0) { fsample = -1.0; }
       x->x_audio_buf[1][x->x_audioin_position]=fsample;
       x->x_audioin_position=(x->x_audioin_position+1)%(MAX_AUDIO_PACKET_SIZE);
       if ( x->x_audioin_position == MAX_AUDIO_PACKET_SIZE-1 )
       {
          post( "pdp_theorout~ : reaching end of audio buffer" );
       }
    }
  }

  return (w+5);
}

static void pdp_theorout_dsp(t_pdp_theorout *x, t_signal **sp)
{
    dsp_add(pdp_theorout_perform, 4, sp[0]->s_vec, sp[1]->s_vec, x, sp[0]->s_n);
}

static void pdp_theorout_process_yv12(t_pdp_theorout *x)
{
  t_pdp     *header = pdp_packet_header(x->x_packet0);
  unsigned char *data   = (unsigned char *)pdp_packet_data(x->x_packet0);
  t_int     i, ret;
  t_int     px, py;
  char      *pY, *pU, *pV;
  struct timeval trec;
  t_int     nbaudiosamples, nbusecs, nbrecorded;
  t_float   fframerate=0.0;
  t_int     precflag;
  ogg_page  apage;
  ogg_page  vpage;
  t_float   **vbuffer;
  double    videotime, audiotime;
  theora_info    lti;
  theora_comment ltc;
  ogg_packet logp, logp2;


    if ( ( (int)(header->info.image.width) != x->x_vwidth ) || 
         ( (int)(header->info.image.height) != x->x_vheight ) || 
         ( x->x_newfile ) )
    {
       precflag = x->x_recflag;
       x->x_recflag = 0;
       pdp_theorout_free_ressources( x );
       pdp_theorout_shutdown_encoder( x );
       x->x_vwidth = header->info.image.width;
       x->x_vheight = header->info.image.height;
       x->x_vsize = x->x_vwidth*x->x_vheight;
       x->x_tvwidth=((x->x_vwidth + 15) >>4)<<4;
       x->x_tvheight=((x->x_vheight + 15) >>4)<<4;
       pdp_theorout_allocate( x );
       if ( x->x_tzero.tv_sec != 0 )
       {
         pdp_theorout_init_encoder( x );
         pdp_theorout_write_headers( x );
       }
       x->x_recflag = precflag;
       x->x_newfile = 0;
    }

    if ( x->x_tzero.tv_sec == 0 )
    {
      if ( gettimeofday(&x->x_tzero, NULL) == -1)
      {
         post("pdp_theorout~ : could get initial time" );
      }
    }
 
    x->x_frames++;

    // calculate current framerate
    if ( gettimeofday(&x->x_tcurrent, NULL) == -1)
    {
       post("pdp_theorout~ : could get current time" );
    }

    // calculate frame rate if it hasn't been set
    if ( ( x->x_tcurrent.tv_sec - x->x_tzero.tv_sec ) > 0 )
    {
      x->x_framerate = x->x_frames / ( x->x_tcurrent.tv_sec - x->x_tzero.tv_sec );
    }
    else
    {
      x->x_framerate = DEFAULT_FRAME_RATE;
    }

    if ( x->x_frameswritten == 0 )
    {
      if ( gettimeofday(&x->x_tlastrec, NULL) == -1)
      {
         post("pdp_theorout~ : could set start time" );
      }
    }

    pY = x->x_yuvbuffer.y;
    memcpy( (void*)pY, (void*)&data[0], x->x_vsize );
    pV = x->x_yuvbuffer.v;
    memcpy( (void*)pV, (void*)&data[x->x_vsize], (x->x_vsize>>2) );
    pU = x->x_yuvbuffer.u;
    memcpy( (void*)pU, (void*)&data[x->x_vsize+(x->x_vsize>>2)], (x->x_vsize>>2) );
      
    if ( x->x_tfile && x->x_recflag && !x->x_enduprec)
    {

      if ( ( ret = theora_encode_YUVin( &x->x_theora_state, &x->x_yuvbuffer ) ) != 0 )
      {
         post( "pdp_theorout~ : could not encode yuv image (ret=%d).", ret );
      }  
      else
      {
         // stream one packet
         theora_encode_packetout(&x->x_theora_state, 0, &logp);
         ogg_stream_packetin(&x->x_statet, &logp);
         // post( "pdp_theorout~ : new (theora) ogg packet : bytes:%ld, bos:%ld, eos:%ld, no:%lld",
         //                        logp.bytes, logp.b_o_s, 
         //                        logp.e_o_s, logp.packetno );


         while( ( ret = ogg_stream_pageout(&x->x_statet, &vpage) ) >0 )
         {
           videotime = theora_granule_time(&x->x_theora_state, ogg_page_granulepos(&vpage));
           if ( ( ret = fwrite(vpage.header, 1, vpage.header_len, x->x_tfile) ) <= 0 )
           {
             post( "pdp_theorout~ : could not write headers (ret=%d).", ret );
             perror( "fwrite" );
           }
           x->x_vbytesout+=ret;
           if ( ( ret = fwrite(vpage.body, 1, vpage.body_len, x->x_tfile) ) <= 0 )
           {
             post( "pdp_theorout~ : could not write headers (ret=%d).", ret );
             perror( "fwrite" );
           }
           x->x_vbytesout+=ret;
         }
      }

      // calculate the number of audio samples to output
      if ( gettimeofday(&trec, NULL) == -1)
      {
         post("pdp_theorout~ : could set stop time" );
      }
      // calculate time diff in micro seconds
      nbusecs = ( trec.tv_usec - x->x_tlastrec.tv_usec ) + 
                ( trec.tv_sec - x->x_tlastrec.tv_sec )*1000000;
      nbaudiosamples = (sys_getsr()*1000000)/nbusecs;
      memcpy( &x->x_tlastrec, &trec, sizeof( struct timeval) );

      if ( x->x_audioin_position > nbaudiosamples )
      {
         nbrecorded = nbaudiosamples;
      }
      else
      {
         nbrecorded = x->x_audioin_position;
      }

      vbuffer=vorbis_analysis_buffer( &x->x_dsp_state, nbrecorded );
      memcpy( (void*)&vbuffer[0][0], (void*)&x->x_audio_buf[0][0], nbrecorded*sizeof( t_float ) );
      memcpy( (void*)&vbuffer[1][0], (void*)&x->x_audio_buf[1][0], nbrecorded*sizeof( t_float ) );

      vorbis_analysis_wrote( &x->x_dsp_state, nbrecorded);

      while(vorbis_analysis_blockout( &x->x_dsp_state, &x->x_vorbis_block)==1)
      {

        // analysis, assume we want to use bitrate management
        vorbis_analysis( &x->x_vorbis_block, NULL);
        vorbis_bitrate_addblock( &x->x_vorbis_block );

        // weld packets into the bitstream 
        while(vorbis_bitrate_flushpacket( &x->x_dsp_state, &logp2))
        {
          ogg_stream_packetin( &x->x_statev, &logp2);
        }

      }

      while( ogg_stream_pageout( &x->x_statev, &apage) >0 )
      {
        audiotime = vorbis_granule_time(&x->x_dsp_state, ogg_page_granulepos(&apage));
        if ( ( ret = fwrite(apage.header, 1, apage.header_len, x->x_tfile) ) <= 0 )
        {
          post( "pdp_theorout~ : could not write headers (ret=%d).", ret );
          perror( "fwrite" );
        }
        x->x_abytesout+=ret;
        if ( ( ret = fwrite(apage.body, 1, apage.body_len, x->x_tfile) ) <= 0 )
        {
          post( "pdp_theorout~ : could not write headers (ret=%d).", ret );
          perror( "fwrite" );
        }
        x->x_abytesout+=ret;
      }

      memcpy( &x->x_audio_buf[0][0], &x->x_audio_buf[0][nbrecorded], 
                     ( x->x_audioin_position-nbrecorded ) * sizeof( t_float ) );
      memcpy( &x->x_audio_buf[1][0], &x->x_audio_buf[1][nbrecorded], 
                     ( x->x_audioin_position-nbrecorded ) * sizeof( t_float ) );
      x->x_audioin_position -= nbrecorded;
      // post ( "pdp_theorout~ : recorded %d samples.", nbrecorded );
       
      x->x_frameswritten++;

    } 

    if ( x->x_tfile && x->x_enduprec )
    {
      x->x_enduprec = 0;
      post( "pdp_theorout~ : ending up recording." );
      x->x_frameswritten++;

      if ( ( ret = theora_encode_YUVin( &x->x_theora_state, &x->x_yuvbuffer ) ) != 0 )
      {
         post( "pdp_theorout~ : could not encode yuv image (ret=%d).", ret );
      }  
      else
      {
         // stream one packet
         theora_encode_packetout(&x->x_theora_state, 1, &logp);
         ogg_stream_packetin( &x->x_statet, &logp);

         while( ( ret = ogg_stream_pageout( &x->x_statet, &vpage) ) > 0 )
         {
           videotime = theora_granule_time(&x->x_theora_state, ogg_page_granulepos(&vpage));
           if ( ( ret = fwrite(vpage.header, 1, vpage.header_len, x->x_tfile) ) <= 0 )
           {
             post( "pdp_theorout~ : could not write headers (ret=%d).", ret );
             perror( "fwrite" );
           }
           x->x_vbytesout+=ret;
           if ( ( ret = fwrite(vpage.body, 1, vpage.body_len, x->x_tfile) ) <= 0 )
           {
             post( "pdp_theorout~ : could not write headers (ret=%d).", ret );
             perror( "fwrite" );
           }
           x->x_vbytesout+=ret;
         }
      }

      // end up audio stream 
      vorbis_analysis_wrote( &x->x_dsp_state, 0);

      while(vorbis_analysis_blockout( &x->x_dsp_state, &x->x_vorbis_block)==1)
      {
        // analysis, assume we want to use bitrate management
        vorbis_analysis( &x->x_vorbis_block, NULL);
        vorbis_bitrate_addblock( &x->x_vorbis_block);

        // weld packets into the bitstream 
        while(vorbis_bitrate_flushpacket( &x->x_dsp_state, &logp2))
        {
          ogg_stream_packetin( &x->x_statev, &logp2);
        }
      }

      while( ogg_stream_pageout( &x->x_statev, &apage) >0 )
      {
        audiotime = vorbis_granule_time(&x->x_dsp_state, ogg_page_granulepos(&apage));
        if ( ( ret = fwrite(apage.header, 1, apage.header_len, x->x_tfile) ) <= 0 )
        {
          post( "pdp_theorout~ : could not write headers (ret=%d).", ret );
          perror( "fwrite" );
        }
        x->x_abytesout+=ret;
        if ( ( ret = fwrite(apage.body, 1, apage.body_len, x->x_tfile) ) <= 0 )
        {
          post( "pdp_theorout~ : could not write headers (ret=%d).", ret );
          perror( "fwrite" );
        }
        x->x_abytesout+=ret;
      }

      post("pdp_theorout~ : stop recording");

      pdp_theorout_shutdown_encoder( x );
      pdp_theorout_close(x);
    }

    return;
}

static void pdp_theorout_killpacket(t_pdp_theorout *x)
{
    /* release the packet */
    pdp_packet_mark_unused(x->x_packet0);
    x->x_packet0 = -1;
}

static void pdp_theorout_process(t_pdp_theorout *x)
{
   int encoding;
   t_pdp *header = 0;

   /* check if image data packets are compatible */
   if ( (header = pdp_packet_header(x->x_packet0))
	&& (PDP_BITMAP == header->type)){
    
	/* pdp_theorout_process inputs and write into active inlet */
	switch(pdp_packet_header(x->x_packet0)->info.image.encoding)
        {

	  case PDP_BITMAP_YV12:
            if ( x->x_tfile && x->x_recflag )
            {
              outlet_float( x->x_obj.ob_outlet, x->x_frameswritten );
            }
            pdp_queue_add(x, pdp_theorout_process_yv12, pdp_theorout_killpacket, &x->x_queue_id);
	    break;

	  default:
	    /* don't know the type, so dont pdp_theorout_process */
	    break;
	    
	}
    }

}

static void pdp_theorout_input_0(t_pdp_theorout *x, t_symbol *s, t_floatarg f)
{
    /* if this is a register_ro message or register_rw message, register with packet factory */

    if (s== gensym("register_rw"))
    {
        x->x_dropped = pdp_packet_convert_ro_or_drop(&x->x_packet0, (int)f, pdp_gensym("bitmap/yv12/*") );
    }

    if ((s == gensym("process")) && (-1 != x->x_packet0) && (!x->x_dropped))
    {
        /* add the process method and callback to the process queue */
        pdp_theorout_process(x);
    }

}

static void pdp_theorout_free(t_pdp_theorout *x)
{
  int i;

    pdp_queue_finish(x->x_queue_id);
    pdp_packet_mark_unused(x->x_packet0);
    // close video file if existing
    pdp_theorout_close(x);
    for ( i=0; i<x->x_channels; i++)
    {
       if ( x->x_audio_buf[i] ) freebytes( x->x_audio_buf[i], MAX_AUDIO_PACKET_SIZE*sizeof(t_float) );
    }
    if ( x->x_audio_buf ) freebytes( x->x_audio_buf, x->x_channels*sizeof(t_float*) );
    
}

t_class *pdp_theorout_class;

void *pdp_theorout_new(void)
{
  t_int i;

    t_pdp_theorout *x = (t_pdp_theorout *)pd_new(pdp_theorout_class);
    inlet_new (&x->x_obj, &x->x_obj.ob_pd, gensym ("signal"), gensym ("signal"));
    outlet_new (&x->x_obj, &s_float);

    x->x_packet0 = -1;
    x->x_packet1 = -1;
    x->x_queue_id = -1;

    x->x_tfile = NULL;
    x->x_yuvbuffer.y = NULL;
    x->x_yuvbuffer.u = NULL;
    x->x_yuvbuffer.v = NULL;

     /* audio defaults */
    x->x_samplerate = sys_getsr();
    x->x_channels = DEFAULT_CHANNELS;
    x->x_bits = DEFAULT_BITS;

    x->x_framerate = DEFAULT_FRAME_RATE;
    x->x_vkbps = DEFAULT_VIDEO_BITRATE;
    x->x_vquality = DEFAULT_VIDEO_QUALITY;
    x->x_akbps = DEFAULT_AUDIO_BITRATE;
    x->x_aquality = DEFAULT_AUDIO_QUALITY;

    x->x_audio_buf = (t_float**) getbytes( x->x_channels*sizeof(t_float*) );
    for ( i=0; i<x->x_channels; i++)
    {
       x->x_audio_buf[i] = (t_float*) getbytes( MAX_AUDIO_PACKET_SIZE*sizeof(t_float) );
    }

    x->x_newfile = 0;
    x->x_frames = 0;
    x->x_frameswritten = 0;

    x->x_tzero.tv_sec = 0;

    return (void *)x;
}


#ifdef __cplusplus
extern "C"
{
#endif


void pdp_theorout_tilde_setup(void)
{
    // post( pdp_theorout_version );
    pdp_theorout_class = class_new(gensym("pdp_theorout~"), (t_newmethod)pdp_theorout_new,
    	(t_method)pdp_theorout_free, sizeof(t_pdp_theorout), 0, A_NULL);

    CLASS_MAINSIGNALIN(pdp_theorout_class, t_pdp_theorout, x_f );
    class_addmethod(pdp_theorout_class, (t_method)pdp_theorout_dsp, gensym("dsp"), 0);
    class_addmethod(pdp_theorout_class, (t_method)pdp_theorout_input_0, gensym("pdp"),  A_SYMBOL, A_DEFFLOAT, A_NULL);
    class_addmethod(pdp_theorout_class, (t_method)pdp_theorout_open, gensym("open"), A_SYMBOL, A_NULL);
    class_addmethod(pdp_theorout_class, (t_method)pdp_theorout_close, gensym("close"), A_NULL);
    class_addmethod(pdp_theorout_class, (t_method)pdp_theorout_start, gensym("start"), A_NULL);
    class_addmethod(pdp_theorout_class, (t_method)pdp_theorout_stop, gensym("stop"), A_NULL);
    class_addmethod(pdp_theorout_class, (t_method)pdp_theorout_abitrate, gensym("audiobitrate"), A_FLOAT, A_NULL);
    class_addmethod(pdp_theorout_class, (t_method)pdp_theorout_vbitrate, gensym("videobitrate"), A_FLOAT, A_NULL);
    class_addmethod(pdp_theorout_class, (t_method)pdp_theorout_aquality, gensym("audioquality"), A_FLOAT, A_NULL);
    class_addmethod(pdp_theorout_class, (t_method)pdp_theorout_vquality, gensym("videoquality"), A_FLOAT, A_NULL);
    class_sethelpsymbol( pdp_theorout_class, gensym("pdp_theorout~.pd") );

}

#ifdef __cplusplus
}
#endif
