/*
 * audio_decoder.c: The audio decoder wrapper
 *
 * See the README file for copyright information and how to reach the author.
 *
 * $Id: audio_decoder.c,v 1.5 2005/12/23 09:10:56 cm Exp $
 */

#include <unistd.h>
#include <string.h>
#include <malloc.h>
#include "audio_decoder.h"

// --- cAudioDecoders ----------------------------------------------------------

cAudioDecoders::cAudioDecoders() {
}

cAudioDecoders::~cAudioDecoders() {
}

#if defined(PLUGIN_DAAP)
cAudioDecoder *cAudioDecoders::Get(const char *format, const cMediaDatabaseBlob *blob) const {
  if (NULL != blob) {
    if (0 == strcasecmp(format, "MP3"))
      return new cMp3Decoder(blob);
#ifdef DECODER_FAAD
    else if (0 == strcasecmp(format, "MP4") ||
             0 == strcasecmp(format, "M4A") ||
             0 == strcasecmp(format, "M4P"))
      return new cMp4AACDecoder(blob);
#endif
  }
  return NULL;
}
#elif defined(PLUGIN_CDDA)
cAudioDecoder *cAudioDecoders::Get(const char *format, cCddaTrack *track) const {
  if (NULL != track) {
    if (0 == strcasecmp(format, "CDA"))
      return new cCdaDecoder(track);
  }
  return NULL;
}
#endif

// --- cAudioDecoder -----------------------------------------------------------

#if defined(PLUGIN_DAAP)
cAudioDecoder::cAudioDecoder(const cMediaDatabaseBlob *blob) : mpo_blob(blob), mo_PCMBuffer(PCM_BUFFER_SIZE) {
}
#elif defined(PLUGIN_CDDA)
cAudioDecoder::cAudioDecoder(cCddaTrack *track) : mpo_track(track), mo_PCMBuffer(PCM_BUFFER_SIZE) {
}
#endif

cAudioDecoder::~cAudioDecoder() {
#if defined(PLUGIN_DAAP)
  delete mpo_blob;
#elif defined(PLUGIN_CDDA)
  // do not delete mpo_track
#endif
}

#if defined(PLUGIN_DAAP)

// --- cMp3Decoder -------------------------------------------------------------

// cMp3Decoder will take ownership of the blob and will delete it when necessary
cMp3Decoder::cMp3Decoder(const cMediaDatabaseBlob *blob) : cAudioDecoder(blob),
  m_state(dsNormal), m_channels(2), m_time(0), m_dataReadPos(0),
  m_bitRate(0), m_sampleRate(44100) {
  SetDescription("DAAP Mp3Decoder");
  Initialize();
  Start();
}

cMp3Decoder::~cMp3Decoder() {
  Cancel(3);
}

const unsigned int cMp3Decoder::MinScanFrames = 50;

void cMp3Decoder::Initialize(void) {
  struct mad_stream stream;
  struct mad_frame frame;

  const uint8_t *p_data = mpo_blob->Get();
  unsigned char inputBuffer[INPUT_BUFFER_SIZE + MAD_BUFFER_GUARD]; 
  unsigned long dataLength = mpo_blob->GetLength(), sumBitRate = 0, numFrames = 0;

  mad_stream_init(&stream);
  mad_frame_init(&frame);
  m_dataReadPos = dataLength >> 1;

  while (MinScanFrames > numFrames) {
    if (NULL == stream.buffer || MAD_ERROR_BUFLEN == stream.error) {
      long readSize = 0;
      unsigned long remaining = 0;
      unsigned char *inputPtr = NULL;
      const unsigned char *readPtr = NULL;

      if (NULL != stream.next_frame) {
        remaining = stream.bufend - stream.next_frame;
        memmove(inputBuffer, stream.next_frame, remaining);
        inputPtr = inputBuffer + remaining;
        readSize = INPUT_BUFFER_SIZE - remaining;
      } else {
        remaining = 0;
        inputPtr = inputBuffer;
        readSize = INPUT_BUFFER_SIZE;
      }

      if (m_dataReadPos + readSize > dataLength)
        readSize = dataLength - m_dataReadPos;

      readPtr = p_data + m_dataReadPos;
      m_dataReadPos += readSize;
      memcpy(inputPtr, readPtr, readSize);

      mad_stream_buffer(&stream, inputBuffer, readSize+remaining);
      stream.error = MAD_ERROR_NONE;
    }

    if (mad_frame_decode(&frame, &stream)) {
      if (MAD_RECOVERABLE(stream.error))
        // skip faulty part and resync to next frame
        continue;
      else {
        if (MAD_ERROR_BUFLEN == stream.error)
          // more data please
          continue;
        else
          // panic
          break;
      }
    }

    numFrames++;
    sumBitRate += frame.header.bitrate;
  } // while ()

  mad_frame_finish(&frame);
  mad_stream_finish(&stream);

  m_bitRate = sumBitRate / numFrames;
  m_time = long(float(dataLength << 3) / m_bitRate * 1000);
}

/*
  the method MADFixedToSshort is based on the code of the madplay project:
  madplay - MPEG audio decoder and player
  Copyright (C) 2000-2004 Robert Leslie
  clip() & audio_linear_round() in audio.c
*/
signed short cMp3Decoder::MADFixedToSshort(mad_fixed_t sample) const {
  /* round - 16bits */
  sample += (1L << (MAD_F_FRACBITS - 16));

  /* clip */
  if (sample > MAD_F_ONE - 1)
    sample = MAD_F_ONE - 1;
  if (sample < -MAD_F_ONE)
    sample = -MAD_F_ONE;

  /* quantize and scale */
  return sample >> (MAD_F_FRACBITS + 1 - 16);
}

/*
  the code in the Action() method is based on the madlld.c source by:
  (c) 2001-2004 Bertrand Petit
  URL: http://www.bsd-dk.dk/%7Eelrond/audio/madlld
*/
void cMp3Decoder::Action() {
  struct mad_stream stream;
  struct mad_frame frame;
  struct mad_synth synth;

  const uint8_t *p_data = mpo_blob->Get();
  unsigned char inputBuffer[INPUT_BUFFER_SIZE + MAD_BUFFER_GUARD], 
                outputBuffer[OUTPUT_BUFFER_SIZE],
                *outputPtr = outputBuffer;
  unsigned long outputSize = 0, dataLength = mpo_blob->GetLength();

  mad_stream_init(&stream);
  mad_frame_init(&frame);
  mad_synth_init(&synth);
  mad_timer_reset(&m_timer);
  m_dataReadPos = 0;

#ifdef DEBUG
  printf("%.16s (%d) starting decoder\n", __FILE__, __LINE__);
#endif

  while (Running() && (m_dataReadPos != dataLength || MAD_ERROR_BUFLEN != stream.error)) {
    if (! outputSize) {
      if (NULL == stream.buffer || MAD_ERROR_BUFLEN == stream.error
                                || dsReSync == m_state) {
        long readSize = 0;
        unsigned long remaining = 0;
        unsigned char *inputPtr = NULL;
        const unsigned char *readPtr = NULL;

        LOCK_THREAD;

        if (NULL != stream.next_frame && dsReSync != m_state) {
          remaining = stream.bufend - stream.next_frame;
          memmove(inputBuffer, stream.next_frame, remaining);
          inputPtr = inputBuffer + remaining;
          readSize = INPUT_BUFFER_SIZE - remaining;
        } else {
          remaining = 0;
          inputPtr = inputBuffer;
          readSize = INPUT_BUFFER_SIZE;
        }

        if (m_dataReadPos + readSize > dataLength)
          readSize = dataLength - m_dataReadPos;

        readPtr = p_data + m_dataReadPos;
        m_dataReadPos += readSize;
        memcpy(inputPtr, readPtr, readSize);

        mad_stream_buffer(&stream, inputBuffer, readSize+remaining);
        stream.error = MAD_ERROR_NONE;

        if (dsReSync == m_state) {
          m_state = dsNormal;
          mad_timer_set(&m_timer, ((readPtr - p_data) << 3) / m_bitRate,
                                (((readPtr - p_data) << 3) / m_bitRate) % 60,
                                frame.header.samplerate);
        }
      }

      if (dsReSync == m_state) {
        // http://www.mars.org/mailman/public/mad-dev/2001-August/000321.html
        mad_frame_mute(&frame);
        mad_synth_mute(&synth);
      } else {
        if (mad_frame_decode(&frame, &stream)){
          if (MAD_RECOVERABLE(stream.error))
            // skip faulty part and resync to next frame
            continue;
          else {
            if (MAD_ERROR_BUFLEN == stream.error)
              // more data please
              continue;
            else
              // panic
              break;
          }
        }

        mad_timer_add(&m_timer, frame.header.duration);

        mad_synth_frame(&synth, &frame);

        m_channels = 2; // always two channels (see below); synth.pcm.channels
        m_sampleRate = synth.pcm.samplerate;

        for (int i = 0; i < synth.pcm.length; i++) {
          signed short sample = 0;

          // left channel
          sample = MADFixedToSshort(synth.pcm.samples[0][i]);
          // store to buffer and convert to unsigned
          *(outputPtr++) = sample >> 8;
          *(outputPtr++) = sample & 0xFF;

          // right channel
          if (2 == MAD_NCHANNELS(&frame.header))
            sample = MADFixedToSshort(synth.pcm.samples[1][i]);

          // store to buffer and convert to unsigned
          *(outputPtr++) = sample >> 8;
          *(outputPtr++) = sample & 0xFF;
        }
        outputSize = outputPtr - outputBuffer;
        outputPtr = outputBuffer;
      }
    } // if (! outputSize)

    if (outputSize) {
      unsigned long written = mo_PCMBuffer.Put(outputPtr, outputSize);

      if (written != outputSize)
        outputPtr += written;
      else
        outputPtr = outputBuffer;

      outputSize -= written;
    }

    if ((mo_PCMBuffer.Size() / 10) > mo_PCMBuffer.Free()) {
//#ifdef DEBUG
//      printf("%.16s (%d) buffer full - sleeping\n", __FILE__, __LINE__);
//#endif
      cCondWait::SleepMs(1000);
    }
  } // while (Running())

#ifdef DEBUG
  printf("%.16s (%d) stopping decoder\n", __FILE__, __LINE__);
#endif

  mad_synth_finish(&synth);
  mad_frame_finish(&frame);
  mad_stream_finish(&stream);
}

void cMp3Decoder::Seek(long offset, eDecoderSeek whence) {
  long i = 0;

  if (dsCurrent == whence)
    i = m_dataReadPos - INPUT_BUFFER_SIZE + offset;
  else if (dsSet == whence)
    i = offset;
  else
    return;

  if (i < 0)
    i = 0;

  if ((unsigned long)i < mpo_blob->GetLength()) {
    LOCK_THREAD;

    m_state = dsReSync;
    m_dataReadPos = i;
    mo_PCMBuffer.Clear();
  }
}

#ifdef DECODER_FAAD

/*
  the cMp4ACCDecoder is based on the source code of the xmms plugin included in
  the faad2 package.
  (c) 2004-??? by ciberfred and ???
  URL: ???
*/

// --- fMp4ff ------------------------------------------------------------------

uint32_t fMp4ffRead_Decoder(void *user_data, void *buffer, uint32_t length) {
  cMp4AACDecoder *p_decoder = (cMp4AACDecoder *)user_data;

  uint32_t l = p_decoder->m_dataReadPos + length < p_decoder->mpo_blob->GetLength() ?
               length : p_decoder->mpo_blob->GetLength() - p_decoder->m_dataReadPos;

  memcpy(buffer, p_decoder->mpo_blob->Get() + p_decoder->m_dataReadPos, l);
  p_decoder->m_dataReadPos += l;

  return l;
}

uint32_t fMp4ffSeek_Decoder(void *user_data, uint64_t position) {
  cMp4AACDecoder *p_decoder = (cMp4AACDecoder *)user_data;

  if (0 <= position && position <= p_decoder->mpo_blob->GetLength())
    p_decoder->m_dataReadPos = position;

  return 0;
}

// --- cMp4AACDecoder ----------------------------------------------------------

cMp4AACDecoder::cMp4AACDecoder(const cMediaDatabaseBlob *blob) : cAudioDecoder(blob),
  m_curTime(0), m_channels(2), m_curSample(0), m_maxSamples(0), m_selTrack(0),
  m_time(0), m_dataReadPos(0), m_avgBitRate(0), m_sampleRate(44100), mp_mp4(NULL) {
  mp_mp4cb = new mp4ff_callback_t;
  memset(mp_mp4cb, 0x00, sizeof(mp4ff_callback_t));
  mp_mp4cb->read = fMp4ffRead_Decoder;
  mp_mp4cb->seek = fMp4ffSeek_Decoder;
  mp_mp4cb->user_data = this;

  SetDescription("DAAP Mp4AACDecoder");
  Start();
}

cMp4AACDecoder::~cMp4AACDecoder() {
  Cancel(3);

  delete mp_mp4cb;
}

void cMp4AACDecoder::Action(void) {
  mp_mp4 = mp4ff_open_read(mp_mp4cb);

  if (NULL != mp_mp4) {
    unsigned char *buffer = NULL;
    unsigned int bufferLen = 0;
    int32_t numTracks = mp4ff_total_tracks(mp_mp4);

    for (m_selTrack = 0; m_selTrack < numTracks; m_selTrack++) {
      mp4ff_get_decoder_config(mp_mp4, m_selTrack, &buffer, &bufferLen);

      if (NULL != buffer) {
        mp4AudioSpecificConfig mp4ASC;
        if (0 <= NeAACDecAudioSpecificConfig(buffer, bufferLen, &mp4ASC))
          // do not free buffer (the decoder config), it is needed for init2
          break;
        else {
          free(buffer);
          buffer = NULL;
          bufferLen = 0;
        }
      }
    }

    if (NULL != buffer) {
      NeAACDecHandle decoder = NeAACDecOpen();

#ifdef DEBUG
      printf("%.16s (%d) starting decoder\n", __FILE__, __LINE__);
#endif

      if(0 == NeAACDecInit2(decoder, buffer, bufferLen, &m_sampleRate, &m_channels)) {
        free(buffer);

        bool error = false;
        uint8_t *outputPtr = NULL;
        unsigned long outputSize = 0;
        NeAACDecFrameInfo frameInfo;

        m_maxSamples = mp4ff_num_samples(mp_mp4, m_selTrack);
        m_avgBitRate = mp4ff_get_avg_bitrate(mp_mp4, m_selTrack);
        m_time = long(float(mp4ff_get_track_duration(mp_mp4, m_selTrack)) / m_sampleRate * 1000);

        while (Running() && m_curSample <= m_maxSamples && ! error) {
          if (! outputSize) {
            LOCK_THREAD;

            buffer = NULL;
            bufferLen = 0;
            if (0 < mp4ff_read_sample(mp_mp4, m_selTrack, m_curSample++, &buffer, &bufferLen)) {
              m_curTime += float(mp4ff_get_sample_duration(mp_mp4, m_selTrack, m_curSample - 1)) / m_sampleRate * 1000;
              outputPtr = (uint8_t *)NeAACDecDecode(decoder, &frameInfo, buffer, bufferLen);

              if (0 == frameInfo.error) {
                outputSize = frameInfo.samples * frameInfo.channels;

                // swap endianess
                for (unsigned long n = 0; n < outputSize; n += 2)
                  *((uint16_t *)(outputPtr+n)) = ((uint16_t) (((uint16_t) *((uint16_t *)(outputPtr+n)) & (uint16_t) 0x00ffU) << 8) |
                                                             (((uint16_t) *((uint16_t *)(outputPtr+n)) & (uint16_t) 0xff00U) >> 8) );
              } else {
                printf("%.16s (%d) fI.error=%s\n", __FILE__, __LINE__, NeAACDecGetErrorMessage(frameInfo.error));
                error = true;
              }
            }
            free(buffer);
          }

          if (outputSize) {
            unsigned long written = mo_PCMBuffer.Put(outputPtr, outputSize);

            if (written != outputSize)
              outputPtr += written;
            else
              outputPtr = NULL;

            outputSize -= written;
          }

          if ((mo_PCMBuffer.Size() / 10) > mo_PCMBuffer.Free())
            cCondWait::SleepMs(1000);
        } // while ()
      } else
        free(buffer);

#ifdef DEBUG
      printf("%.16s (%d) stopping decoder\n", __FILE__, __LINE__);
#endif

      NeAACDecClose(decoder);
    }

    mp4ff_close(mp_mp4);
  }
}

void cMp4AACDecoder::Seek(long offset, eDecoderSeek whence) {
  long i = 0;

  if (dsCurrent == whence)
    i = m_dataReadPos + offset;
  else if (dsSet == whence)
    i = offset;
  else
    return;

  if (i < 0)
    i = 1;

  if ((unsigned long)i < mpo_blob->GetLength()) {
    LOCK_THREAD;

    m_curSample = i / (mpo_blob->GetLength() / m_maxSamples);
    m_curTime = float(m_curSample) * mp4ff_get_sample_duration(mp_mp4, m_selTrack, m_curSample - 1) / mp4ff_time_scale(mp_mp4, m_selTrack) * 1000;

    mo_PCMBuffer.Clear();
  }
}

#endif // DECODER_FAAD

#endif // PLUGIN_DAAP

#if defined(PLUGIN_CDDA)

// --- cCdaDecoder -------------------------------------------------------------

cCdaDecoder::cCdaDecoder(cCddaTrack *track) : cAudioDecoder(track),
  m_time(0), m_curTime(0), m_avgBitRate(0), m_curFrame(0), m_maxFrames(0) {
  SetDescription("CDDA CdaDecoder");
  Start();
}

cCdaDecoder::~cCdaDecoder() {
  Cancel(3);
}

void cCdaDecoder::Action(void) {
#ifdef DEBUG
  printf("%.16s (%d) starting decoder\n", __FILE__, __LINE__);
#endif

  if (mpo_track->isAudio()) {
    unsigned long outputSize = 0;
    uint8_t *outputPtr = NULL;
    m_maxFrames = mpo_track->GetSectorCount();
    m_avgBitRate = 44100 * mpo_track->GetChannels() * 16;
    m_time = DataSizeToMSeconds(mpo_track->GetSize());

    while (Running() && m_curFrame <= m_maxFrames) {
      if (! outputSize) {
        LOCK_THREAD;

        int n = CDDA_READ_MAX_SECTORS;
        outputPtr = mpo_track->ReadSector(m_curFrame, n);
        m_curFrame += n;
        if (NULL != outputPtr) {
          outputSize = n * CDIO_CD_FRAMESIZE_RAW;
          m_curTime += DataSizeToMSeconds(outputSize);

          // swap endianess
          for (unsigned long n = 0; n < outputSize; n += 2)
            *((uint16_t *)(outputPtr+n)) = ((uint16_t) (((uint16_t) *((uint16_t *)(outputPtr+n)) & (uint16_t) 0x00ffU) << 8) |
                                           (((uint16_t) *((uint16_t *)(outputPtr+n)) & (uint16_t) 0xff00U) >> 8) );
        }
      }

      if (outputSize) {
        unsigned long written = mo_PCMBuffer.Put(outputPtr, outputSize);

        if (written != outputSize)
          outputPtr += written;
        else
          outputPtr = NULL;

        outputSize -= written;
      }

      if ((mo_PCMBuffer.Size() / 10) > mo_PCMBuffer.Free())
        cCondWait::SleepMs(1000);
    } // while ()
  } // if (mpo_track->isAudio())

#ifdef DEBUG
      printf("%.16s (%d) stopping decoder\n", __FILE__, __LINE__);
#endif
}

void cCdaDecoder::Seek(long offset, eDecoderSeek whence) {
  long i = 0;

  if (dsCurrent == whence)
    i = m_curFrame + offset / CDIO_CD_FRAMESIZE_RAW;
  else if (dsSet == whence)
    i = offset / CDIO_CD_FRAMESIZE_RAW;
  else
    return;

  if (i < 0)
    i = 1;

  if ((unsigned long)i < m_maxFrames) {
    LOCK_THREAD;

    m_curFrame = i;
    m_curTime = DataSizeToMSeconds(i * CDIO_CD_FRAMESIZE_RAW);

    mo_PCMBuffer.Clear();
  }
}

#endif // PLUGIN_CDDA
