/*  mpbuf.cpp
    buffered mpeg stream functions.

    17.09.2001: tk, stupid bug fixed. One assert for bufCount wasn't
                    enclosed in a enable/disable, and caused sporadic
                    errors (after ~2h of mp3 streaming..).
    15.09.2001: tk, added disable/enable pairs, because the
                    long integers could get updated by the callback
                    in the middle of an access. (it really matters!)
    12.09.2001: tk, the function calls are now similar to the
                    DirectX Audio DSB interface. It's actually
                    quite nice, and fits perfectly for the task.
    10.08.2001: tk, initial implementation.

    Copyright (c)2000 by Thomas Kindler, thomas.kindler@gmx.de

    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. Read the
    full License at http://www.gnu.org/copyleft for more details.
*/

// include files -----
//
#include <stdlib.h>
#include <stdio.h>
#include <alloc.h>
#include <assert.h>
#include <mem.h>
#include "clib/rtos.h"
#include "vs1001.h"
#include "mpbuf.h"

// farmalloc'd buffer for mpeg data
//
static char huge  *bufPtr;
static long        bufSize;

// current playing position, and number of bytes.
//
// ** IMPORTANT **
// The long volatiles may only be read or modified inside
// a disable/enable section. The callback function might get
// called in the middle of an access otherwise.
//
static volatile long  bufHead, bufCount;
static volatile int   bufState;
static long           bufThreshold;


/**
 * Callback function for PlayMpegAsync(). This function
 * is called after a block of data has been finished.
 *
 * \param  param  size of last block (cast to unsigned long).
 */
void callback(void *param)
{
  bufHead  += (long)param;
  bufHead  %= bufSize;
  bufCount -= (long)param;

  if (bufState == BUF_STOPPING)
    bufState = BUF_STOPPED;
  if (bufState == BUF_PLAYING )
    BUF_Play();
}


/**
 * Get total size of the buffer.
 *
 * \return  number of allocated bytes.
 */
long BUF_GetSize()
{
  return  bufSize;
}


/**
 * Get number of bytes in the buffer.
 * \return  number of bytes.
 */
long BUF_GetCount()
{
  disable();
  long count = bufCount;
  enable();
  return  count;
}


/**
 * Get number of free bytes in the buffer. You should
 * call this function to find out how many bytes can
 * (at least) be written by the next BUF_Write() call.
 *
 * \return  number of free bytes in the buffer.
 */
long BUF_GetFreeBytes()
{
  return BUF_GetSize()-BUF_GetCount();
}


/**
 * Get current buffer state. During playback, the
 * buffer can be in various states:
 *
 * - BUF_STOPPED
 *   Buffer playback is completely stopped.
 * - BUF_STOPPING
 *   Playback is just about to stop, and is waiting
 *   for the current DMA transfer to finish.
 * - BUF_PLAYING
 *   Playback is in progress.
 * - BUF_STALLED
 *   The buffer is waiting for more bytes. 
 *
 * \see     BUF_SetThreshold();
 * \return  current buffer state
 */
int  BUF_GetState()
{
  return bufState;
}


/**
 * Set buffer threshold.
 *
 * The threshold value sets the amount of bytes that have to
 * be written into the buffer, to continue playing after a
 * buffer stall. This is useful, if you expect an unreliable
 * data stream, like http-connections over the internet.
 * With a treshold value of, say half the buffer size, you
 * can avoid excessive stuttering while recovering from a
 * buffer stall.
 *
 * \note   the default threshold value equals VS_FIFOSIZE.
 * \param  threshold  number of bytes needed to recover
 *                    from the BUF_STALLED state.
 */
void BUF_SetThreshold(long threshold)
{
  bufThreshold = threshold;
}


/**
 * Get current play and write positions.
 *
 * \note The write position is always some bytes ahead of
 *       the play position. (only in case of an empty buffer,
 *       the write position equals the play position)
 *
 * \param playpos  pointer to variable receiving the
 *                 current play position. (may be NULL)
 * \param writepos poitner to variable receiving the
 *                 current write position. (may be NULL)
 */
void BUF_GetPositions(long *playpos, long *writepos)
{
  disable();
  if (playpos)
    *playpos = bufHead;
  if (writepos)
    *writepos = (bufHead+bufCount) % bufSize;
  enable();
}


/**
 * Get (write only) pointers to a block of bytes in the buffer.
 * Because the buffer is circular, a wrap around may be needed if
 * you want to access a block of bytes. This function calculates
 * the correct pointers into the buffer for a given starting position
 * and length. If you only want pointers to the first portion of the
 * buffer, you can give NULL pointers for buf2 and len2.
 *
 * \note  Please use the returned pointers for write access only.
 *        The implementation of the buffer might change in the
 *        future, and a read may not return valid data. (i plan
 *        to use multiple small buffers instead of one big memory
 *        area for future versions)
 *
 * \param offset  byte offset, may be set to BUF_FROMWRITEPOS.
 * \param length  maximum number of bytes to lock
 * \param buf1    Address of a variable that receives a
 *                pointer to the first part of the buffer
 * \param len1    Address of a variable that receives the
 *                number of bytes in the block at buf1.
 * \param buf2    Address of a variable that receives a
 *                pointer to the second part of the buffer.
 *                If this variable receives a NULL pointer,
 *                a wrap around wasn't needed.
 * \param len1    Address of a variable that receives the
 *                number of bytes in the block at buf2.
 */
void BUF_Lock(long offset, long length,
              char **buf1, long *len1,
              char **buf2, long *len2 )
{
  if (offset == BUF_FROMWRITEPOS)
    BUF_GetPositions(NULL, &offset);

  offset = offset % bufSize;
  if (length > bufSize)
    length = bufSize;

  *buf1 = &bufPtr[offset];
  *len1 = min(length, bufSize-offset);
  if (buf2 && len2) {
    if (*len1 < length) {
      *buf2 = bufPtr;
      *len2 = length - *len1;
    } else {
      *buf2 = NULL;
      *len2 = 0;
    }
  }
}


/**
 * Unlock the buffer, after bytes have been written.
 * You must call this function after BUF_Lock() to
 * advance the write pointer.
 *
 * \note   If the buffer was stalled (i.e. ran out of bytes),
 *         BUF_Unlock() will only resume playback, after the
 *         buffer treshold is reached.
 * \param  number of bytes written into the buffer.
 */
void BUF_Unlock(long bytesWritten)
{
  disable();
  bufCount += bytesWritten;
  bool   ok = bufCount <= bufSize;
  enable();
  assert(ok);

  // continue playback (if buffer was stalled)
  //
  if (bufState == BUF_STALLED &&
      bufCount >= bufThreshold)
    BUF_Play();
}


/**
 * Write some bytes into the buffer. The BUF_Write() function
 * tries to write a maximum of length bytes at the current write
 * position. This function is only supplied for easy (lazy)
 * application programming, and should not be used because it
 * copies the data. Use the BUF_Lock/BUF_Unlock functions whenever
 * possible.
 * <pre>
 *  data flow using BUF_Write
 *    harddisk --copy--> application --copy--> mpbuf
 *
 *  data flow using BUF_Lock/BUF_Unlock
 *    harddisk --copy--> mpbuf</pre>
 * 
 * \param   src    pointer to data to be copied.
 * \param   length number of bytes to be copied.
 * \return  number of actual bytes written. This may be
 *          less than length bytes, if there wasn't enough
 *          space in the buffer.
 */
unsigned BUF_Write(char *src, unsigned length)
{
  char *buf1, *buf2;
  long  len1,  len2;

  length = min(length, BUF_GetFreeBytes());

  BUF_Lock( BUF_FROMWRITEPOS, length,
            &buf1, &len1, &buf2, &len2);

  memcpy(buf1, src, len1);
  if (buf2)
    memcpy(buf2, &src[len1], len2);
  BUF_Unlock(len1+len2);

  return len1+len2;
}


/**
 * Start buffered playback.
 *   The BUF_Play() function will start playback immediately
 *   if the buffer was in the BUF_STOPPED state. If there are
 *   no bytes in the buffer, it will enter the BUF_STALLED
 *   state instead. (note that the BUF_Play() function will
 *   force playback, even if there are fewer than bufThreshold
 *   bytes in the buffer. The threshold will only be respected,
 *   if the buffer enters the BUF_STALLED state during playback.)
 *
 *   The actual playback is done by the VS_PlayMpegAsync
 *   function, and will play blocks of <= VS_FIFOSIZE bytes.
 *
 */
void BUF_Play()
{
  if (BUF_GetCount() == 0) {
    bufState = BUF_STALLED;
  } else if (VS_IsPlaying()) {
    bufState = BUF_PLAYING;
  } else {
    char *buf1;
    long  len1 = BUF_GetCount();

    if (len1 > VS_FIFOSIZE)
      len1 = VS_FIFOSIZE;

    BUF_Lock(bufHead, len1, &buf1, &len1, NULL, NULL);
    VS_PlayMpegAsync(buf1, len1, callback, (void*)len1);

    bufState = BUF_PLAYING;
  }
}


/**
 * Stop buffered playback.
 *   If the buffer is currently in the playing state, the
 *   playback will stop directly after the current block has
 *   been played.
 */
void BUF_Stop()
{
  if (bufState == BUF_STALLED)
    bufState = BUF_STOPPED;
  else if (bufState == BUF_PLAYING)
    bufState = BUF_STOPPING;
}


/**
 * Play remaining bytes until the buffer is
 * empty.
 */
void BUF_Flush()
{
  BUF_Play();
  while (bufState != BUF_STALLED)
    RTX_Sleep_Time(10);
  BUF_Stop();
}


/**
 * Stop playback, and discard buffer bytes.
 *
 */
void BUF_Clear()
{
  BUF_Stop();
  while (bufState != BUF_STOPPED) {};

  bufCount = 0;
  bufHead  = 0;
}


/**
 * Initialize MPEG-Buffer. BUF_Init first tries to allocate
 * maxsize bytes for its internal buffer; if there is not
 * enough memory, the size is reduced by 16k, until a buffer
 * could be allocated. The minimum buffer size is bounded by
 * minsize.
 *
 * \note    if you want need a specific buffer size,
 *          set minsize = maxsize.
 * \param   minsize  minimum buffer size to allocate.
 * \param   maxsize  maximum buffer size to allocate.
 * \return  number of bytes allocated, or 0 if init failed.
 */
long BUF_Init(long minsize, long maxsize)
{
  printf("BUF_Init: ");

  bufSize = maxsize;
  while (bufSize >= minsize) {
    bufPtr = (char*)farmalloc(bufSize);
    if (bufPtr)
      break;
    bufSize -= 16*1024;
  }

  if (bufPtr) {
    printf( "allocated %ldkb at %p.\n", bufSize>>10, bufPtr );
  } else {
    printf( "couldn't allocate at least %ld bytes.\n", minsize);
    bufSize = 0;
  }

  bufState     = BUF_STOPPED;
  bufHead      = 0;
  bufCount     = 0;
  bufThreshold = VS_FIFOSIZE;
  return bufSize;
}


/**
 * Free all resources used by the MPEG-Buffer.
 *
 */
void BUF_Done()
{
  BUF_Stop();
  if (bufPtr)
    farfree(bufPtr);
  bufSize=0;
}

