/*
** nsvlib.h - NSV file/bitstream reading/writing interface
** 
** Copyright (C) 2001-2002 Nullsoft, Inc.
**
** Confidential Subject to NDA
*/

#ifndef _NSVLIB_H_
#define _NSVLIB_H_

/*********************************************************************
** bitstream classes 
*/

#include "nsvbs.h"


/*********************************************************************
** NSV packeting limits
*/
#define NSV_MAX_AUDIO_LEN 0x8000  // 32kb
#define NSV_MAX_VIDEO_LEN 0x80000 // 512kb
#define NSV_MAX_AUX_LEN   0x8000  // 32kb for each aux stream
#define NSV_MAX_AUXSTREAMS 15     // 15 aux streams maximum


/*********************************************************************
** Constants for setting certain metadata items using addHdrMetaData()
*/
#define METADATANAME_AUTHOR			"Author"
#define METADATANAME_TITLE			"Title"
#define METADATANAME_COPYRIGHT		"Copyright"
#define METADATANAME_COMMENT		"Comment"
#define METADATANAME_PROFILE		"Profile"
#define METADATANAME_FILEID			"File ID"

/*********************************************************************
** NSV type utility functions/macros
*/

/*
** Use NSV_MAKETYPE() to quickly make NSV audio/video/aux types.
** ex: NSV_MAKETYPE('R','G','B','A')
*/
#define NSV_MAKETYPE(A,B,C,D) ((A) | ((B)<<8) | ((C)<<16) | ((D)<<24))

/*
** These functions convert types to and from strings.
*/

/* nsv_type_to_string() converts an NSV type to a string.
 * out must be at least 5 bytes long. If 't' is not a valid type, 
 * then out will be set to an empty string
 * ex: 
 *   char out[5]; 
 *   nsv_type_to_string(NSV_MAKETYPE('R','G','B','A'),out); 
 *   strcmp(out,"RGBA") == 0
 */
void nsv_type_to_string(unsigned int t, char *out);

/* nsv_string_to_type() converts a string to an NSV type. 
 * Returns 0 if the type is not valid.
 * ex: nsv_string_to_type("RGBA") == NSV_MAKETYPE('R','G','B','A')
 */
unsigned int nsv_string_to_type(char *in);


/*********************************************************************
** NSV bitstream packeting/unpacketing classes
*/


/* nsv_Packeter is used to packet audio/video/auxiliary data into
 * a bitstream.
 *
 * ex:
 *   nsv_Packeter p;
 *   nsv_OutBS bs;
 *   p.setVidFmt(NSV_MAKETYPE('R','G','B','A'),320,240,30.0);
 *   p.setAudFmt(NSV_MAKETYPE('P','C','M',' '));
 *   for (;;) {
 *     doEncodeAudioAndVideo();
 *     p.setSyncFrame(is_keyframe);
 *     p.setSyncOffset(av_sync_offset);
 *     p.setAudio(audio_data,audio_len);
 *     p.setVideo(video_data,video_len);
 *     p.clearAuxChannels(); // you can add aux channels if you want
 *     if (p.packet(bs)) error();
 *     int outbuflen;
 *     void *outbuf=bs.get(&outbuflen);
 *     fwrite(outbuf,outbuflen,1,fp); // write output
 *     bs.clear(); // clear bitstream
 *   }
 *
 */

class nsv_Packeter {
  public:
    nsv_Packeter();
    ~nsv_Packeter();

    // init (per file) calls
    void setVidFmt(unsigned int vfmt, unsigned int w, unsigned int h, double frt);
    void setAudFmt(unsigned int afmt) { audfmt=afmt; }

    // per frame calls
    void setSyncFrame(int is_syncframe) { is_sync_frame=is_syncframe; }
    void setSyncOffset(int syncoffs) { syncoffset_cur=syncoffs; }
    void setAudio(void *a, int a_len) { audio=a; audio_len=a_len; }
    void setVideo(void *v, int v_len) { video=v; video_len=v_len; }
    int addAuxChannel(unsigned int fmt, void *data, int data_len) // 0 on success
    {
      if (aux_used >= NSV_MAX_AUXSTREAMS) return -1;
      aux[aux_used]=data;
      aux_len[aux_used]=data_len;
      aux_types[aux_used]=fmt;
      aux_used++;
      return 0;
    }
    void clearAuxChannels() { aux_used=0; }

    int packet(nsv_OutBS &bs); // returns 0 on success

    // some utility getting functions
    unsigned int getAudFmt() { return audfmt; }
    unsigned int getVidFmt() { return vidfmt; }
    unsigned int getWidth() { return width; }
    unsigned int getHeight() { return height; }
    double getFrameRate() { return framerate; }

  private:
    unsigned char framerate_idx;
    unsigned int vidfmt;
    unsigned int audfmt;
    unsigned int width;
    unsigned int height;
    double framerate;
    int syncoffset_cur;

    int aux_used;
    void *aux[NSV_MAX_AUXSTREAMS];
    int aux_len[NSV_MAX_AUXSTREAMS];
    unsigned int aux_types[NSV_MAX_AUXSTREAMS];
    int is_sync_frame;
    void *audio;
    int audio_len;
    void *video;
    int video_len;
};


/* nsv_Unpacketer is used to unpacket a bitstream into audio/video/auxiliary data
 * to decode, use an nsv_InBS object with data, and call unpacket().
 * ex:
 *  nsv_Unpacketer up;
 *  nsv_InBS in;
 *  nsv_InBS videoout, audioout;
 *  up.setVideoOut(&videoout);
 *  up.setAudioOut(&audioout);
 *  for (;;) {
 *    int ret=up.unpacket(in);
 *    if (ret < 0) break; // eof
 *    if (ret > 0) add_data_to_bitstream(&in,ret);
 *    if (!ret) { // got frame
 *      int vl=videoout.getbits(32);
 *      int al=videoout.getbits(32);
 *      char *vd=(char*)videoout.getcurbyteptr();
 *      char *ad=(char*)audioout.getcurbyteptr();
 *      doDecode(vd,vl,ad,al);
 *      videoout.seek(vl*8);
 *      audioout.seek(al*8);
 *      videoout.compact(); // free memory up
 *      audioout.compact(); // free memory up
 *      in.compact(); // free memory up
 *    }
 *  }
 */

class nsv_Unpacketer {
  public:
    nsv_Unpacketer() { reset(); }
    ~nsv_Unpacketer() { }

    void reset(int full=1); // if full, full reset is done. 
                            // if not, then it is a partial reset (ie for seeking)

    // when EOF is set, the unpacketer will fail instead of requesting more data at the
    // end; it will also not require that the next frame be available for sync
    // (normally it looks ahead to verify data)
    void setEof(int eof=1) { m_eof=eof; }
    int getEof() { return m_eof; }

    // use these to set where the unpacketer writes the output of each stream
    // set to NULL to ignore output of that stream
    
    void setAudioOut(nsv_InBS *output=NULL) { m_audiobs=output; }
    // the format of the audio data written to the output is:
    //   32 bits: length of frame
    //   ? bytes: audio data
    // (to read):
    //   int l=output->getbits(32);
    //   decode_audio(output->getcurbyteptr(),l);
    //   output->seek(l*8);


    void setVideoOut(nsv_InBS *output=NULL) { m_videobs=output; }
    // the format of the video data written to the output is:
    //   32 bits: length of frame
    //   ? bytes: video data
    // (to read):
    //   int l=output->getbits(32);
    //   decode_video(output->getcurbyteptr(),l);
    //   output->seek(l*8);

    void setAuxOut(nsv_InBS *output=NULL) { m_auxbs=output; }
    // the format of the aux data written to the output is:
    //   32 bits: length of frame
    //   32 bits: type of aux data
    //   ? bytes: aux data
    // (to read):
    //   int l=output->getbits(32);
    //   int type=output->getbits(32);
    //   decode_aux(output->getcurbyteptr(),l);
    //   output->seek(l*8);
    // aux is different than audio/video in that it includes a 32 bit
    // type value that is not included in the length.


    // returns 0 on success, >0 on needs (at least X bytes) more data, 
    // -1 on error (eof and no header found)
    int unpacket(nsv_InBS &bs);


    // do we have enough sync to determine formats/widths/heights/framerates
    int isValid() { return valid; }
    
    // are we fully synched? 
    int isSynched() { return synched; }

    // get sync offset from when we first synched up
    signed int getSyncOffset() { return (signed int) syncoffset; }

    // get sync offset from current frame (not usually used)
    signed int getCurSyncOffset() { return (signed int) syncoffset_cur; }

    // get video, audio, width, height, framerate formats.
    unsigned int getVidFmt() { return vidfmt; }
    unsigned int getAudFmt() { return audfmt; }
    unsigned int getWidth() { return width; }
    unsigned int getHeight() { return height; }
    double getFrameRate() { return framerate; }
    unsigned char getFrameRateIdx() { return framerate_idx; }

    // is current frame a sync frame?
    int isSynchFrame() { return is_sync_frame; }

  private:
    nsv_InBS *m_audiobs, *m_videobs, *m_auxbs;
    int valid; // contents of stream info are valid for syncing
    int synched; // turns off anal packet checking
    unsigned int vidfmt;
    unsigned int audfmt;
    unsigned int width;
    unsigned int height;
    double framerate;
    int is_sync_frame;
    unsigned char framerate_idx;
    int syncoffset;
    int syncoffset_cur;

    int m_eof;
};


/*********************************************************************
** NSV file header reading/writing functions
*/


typedef struct {
  // header_size is the size of NSV header. nsv_writeheader() and nsv_readheader()
  // will set this automatically
  unsigned int header_size; 

  // file_lenbytes is the size of the NSV bitstream (not including the header size)
  // this can be 0xFFFFFFFF to signify unknown length
  unsigned int file_lenbytes;

  // file_lenms is the length of the NSV bitstream in milliseconds.
  // this can be 0xFFFFFFFF to signify unknown length
  unsigned int file_lenms;

  // metadata_len describes the length of the metadata.
  unsigned int metadata_len;

  // toc_alloc describes the allocated length of the TOC (in entries).
  // set this to zero to use toc_size (recommended).
  unsigned int toc_alloc;

  // toc_size describes the used size of the TOC (in entries)
  // set this to zero to disable the TOC. When using toc_ex,
  // this must be < toc_alloc/2 (if using nsv_writeheader, and
  // toc_size is too big, toc_alloc will be grown automatically.
  unsigned int toc_size;

  // buffer which contains the TOC. this will be automatically 
  // allocated when using nsv_readheader(), but you should allocate
  // this yourself when using nsv_writeheader()
  unsigned int *toc;

  // if used, contains time pairs (in frames) for the offset. this will be 
  // automatically allocated when using nsv_readheader(), but you should allocate
  // this yourself when using nsv_writeheader()
  // DO NOT FREE THIS VALUE IF IT WAS ALLOCATED FROM NSV_READHEADER. :)
  // (it is just an extension of toc, which should be freed with free())
  unsigned int *toc_ex;

  // buffer which contains metadata. allocated when using nsv_readheader(), 
  // but you should allocate this yourself when using nsv_writeheader()
  // note that nsv_readheader() will NULL terminate this buffer.
  void *metadata;

} nsv_fileHeader;

// nsv_writeheader() writes the NSV file header to the bitstream bs.
// the NSV file header will be at LEAST padto bytes long (usually
// you will leave padto to 0)
void nsv_writeheader(nsv_OutBS &bs, nsv_fileHeader *hdr, unsigned int padto);

// nsv_readheader() reads an NSV file header from a bitstream bs.
// if the return value is less than zero, then there is no NSV
// file header in bs. if the return value is zero, the NSV file
// header was succesfully read. if the return value is positive,
// then at least that many more bytes are needed to decode the
// header.
// ex:
//   nsv_InBS bs;
//   nsv_fileHeader hdr;
//   for (;;) {
//      int ret=nsv_readheader(bs,&hdr);
//      if (ret<=0) break;
//      addBytesToBs(bs,ret);
//   }
//   if (hdr.header_size) { we_got_valid_header(&hdr); }
//

int nsv_readheader(nsv_InBS &bs, nsv_fileHeader *hdr);


// nsv_getmetadata() retrieves a metadata item from the metadata 
// block. if that item is not found, NULL is returned.
// Note that the value returned by nsv_getmetadata() has been
// malloc()'d, and you must free() it when you are done.
// ex:
//   char *v=nsv_getmetadata(hdr.metadata,"TITLE");
//   if (v) printf("title=%s\n",v);
//   free(v);
//

char *nsv_getmetadata(void *metadata, char *name);


#endif//_NSVLIB_H_