/*
 * audio_decoder.c: The audio decoder wrapper (to the libmad library)
 *
 * See the README file for copyright information and how to reach the author.
 *
 * $Id: audio_decoder.c,v 1.23 2005/10/31 14:29:13 cm Exp $
 */

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

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

cAudioDecoders::cAudioDecoders() {
}

cAudioDecoders::~cAudioDecoders() {
}

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;
}

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

cAudioDecoder::cAudioDecoder(const cMediaDatabaseBlob *blob) : mpo_blob(blob), mo_PCMBuffer(PCM_BUFFER_SIZE) {
}

cAudioDecoder::~cAudioDecoder() {
  delete mpo_blob;
}

// --- 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_dataReadPos(0), m_numFrames(0), m_sumBitRate(0) {
  SetDescription("DAAP Mp3Decoder");
  Start();
}

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

/*
  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);

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

  while (Running() && m_dataReadPos != dataLength) {
    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;
          m_sumBitRate /= m_numFrames;
          m_numFrames = 1;
          mad_timer_set(&m_timer, ((readPtr - p_data) * 8) / m_sumBitRate,
                                (((readPtr - p_data) * 8) / m_sumBitRate) % 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;
          }
        }

        m_numFrames++;
        m_sumBitRate += frame.header.bitrate;
        mad_timer_add(&m_timer, frame.header.duration);

        mad_synth_frame(&synth, &frame);

        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 + 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(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(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_curSample(0), m_maxSamples(0), m_selTrack(0),
  m_dataReadPos(0), m_avgBitRate(0), mp_mp4(NULL) {
  mp_mp4cb = new mp4ff_callback_t;
  memset(mp_mp4cb, 0x00, sizeof(mp4ff_callback_t));
  mp_mp4cb->read = fMp4ffRead;
  mp_mp4cb->seek = fMp4ffSeek;
  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, channels = 0;
    unsigned int bufferLen = 0;
    unsigned long sampleRate = 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, &sampleRate, &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);

        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)) / float(sampleRate) * float(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) * float(mp4ff_get_sample_duration(mp_mp4, m_selTrack, m_curSample - 1)) / float(mp4ff_time_scale(mp_mp4, m_selTrack)) * float(1000);

    mo_PCMBuffer.Clear();
  }
}

#endif
