/*
 * Copyright (C) 2011 Raphael Coeffic
 *
 * This file is part of SEMS, a free SIP media server.
 *
 * SEMS 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 released under
 * the GPL with the additional exemption that compiling, linking,
 * and/or using OpenSSL is allowed.
 *
 * For a license to use the SEMS software under conditions
 * other than those described here, or to purchase support for this
 * software, please contact iptel.org by e-mail at the following addresses:
 *    info@iptel.org
 *
 * SEMS 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "RtmpAudio.h"
#include "RtmpSender.h"

#include "amci/codecs.h"

#define SPEEX_WB_SAMPLES_PER_FRAME 320
#define SPEEX_WB_SAMPLE_RATE 16000

#include <fcntl.h>

static void dump_audio(RTMPPacket *packet)
{
  static int dump_fd=0;
  if(dump_fd == 0){
    dump_fd = open("speex_in.raw",O_WRONLY|O_CREAT|O_TRUNC,
		   S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH);
    if(dump_fd < 0)
      ERROR("could not open speex_in.raw: %s\n",strerror(errno));
  }
  if(dump_fd < 0) return;

  uint32_t pkg_size = packet->m_nBodySize-1;
  write(dump_fd,&pkg_size,sizeof(uint32_t));
  write(dump_fd,packet->m_body+1,pkg_size);
}

static void dump_audio(unsigned char* buffer, unsigned int size)
{
  static int dump_fd=0;
  if(dump_fd == 0){
    dump_fd = open("pcm_in.raw",O_WRONLY|O_CREAT|O_TRUNC,
		   S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH);
    if(dump_fd < 0)
      ERROR("could not open pcm_in.raw: %s\n",strerror(errno));
  }
  if(dump_fd < 0) return;

  write(dump_fd,buffer,size);
}


RtmpAudio::RtmpAudio(RtmpSender* s)
  : AmAudio(new AmAudioFormat(CODEC_SPEEX_WB,SPEEX_WB_SAMPLE_RATE)),
    playout_buffer(this,SPEEX_WB_SAMPLE_RATE), 
    sender(s), play_stream_id(0),
    recv_offset_i(false), recv_rtp_offset(0), recv_rtmp_offset(0),
    send_offset_i(false), send_rtmp_offset(0)
{
}

RtmpAudio::~RtmpAudio()
{
}

// returns bytes read, else -1 if error (0 is OK)
int RtmpAudio::get(unsigned long long system_ts, unsigned char* buffer, 
		   int output_sample_rate, unsigned int nb_samples)
{
  // - buffer RTMP audio
  // - read from RTMP recv buffer

  unsigned int user_ts = scaleSystemTS(system_ts);

  //DBG("get(%u, %u)\n",user_ts,nb_samples);
  process_recv_queue(user_ts);

  nb_samples = (unsigned int)((float)nb_samples * (float)getSampleRate()
			     / (float)output_sample_rate);

  u_int32_t size =
    PCM16_S2B(playout_buffer.read(user_ts,
				  (ShortSample*)((unsigned char*)samples),
				  nb_samples));

  if(output_sample_rate != getSampleRate()) {
    size = resampleOutput((unsigned char*)samples, size,
			  getSampleRate(), output_sample_rate);
  }
  
  memcpy(buffer,(unsigned char*)samples,size);

  return size;
}

// returns bytes written, else -1 if error (0 is OK)
int RtmpAudio::put(unsigned long long system_ts, unsigned char* buffer, 
		   int input_sample_rate, unsigned int size)
{
  if(!size){
    return 0;
  }

  //dump_audio((unsigned char*)buffer,size);

  // copy into internal buffer
  memcpy((unsigned char*)samples,buffer,size);
  size = resampleInput((unsigned char*)samples, size, 
		       input_sample_rate, getSampleRate());

  int s = encode(size);
  //DBG("s = %i\n",s);
  
  if(s<=0){
    return s;
  }

  return send(scaleSystemTS(system_ts),(unsigned int)s);
}

int RtmpAudio::send(unsigned int user_ts, unsigned int size)
{
  m_sender.lock();

  if(!sender || !play_stream_id) {
    //DBG("!sender || !play_stream_id");
    m_sender.unlock();
    return 0;
  }

  // - generate a new RTMP audio packet
  // - send packet

  RTMPPacket packet;
  RTMPPacket_Reset(&packet);

  packet.m_headerType  = send_offset_i ? 
    RTMP_PACKET_SIZE_MEDIUM 
    : RTMP_PACKET_SIZE_LARGE;

  packet.m_packetType  = RTMP_PACKET_TYPE_AUDIO;
  packet.m_nChannel    = 4;//TODO
  packet.m_nInfoField2 = play_stream_id;

  if(!send_offset_i){
    send_rtmp_offset = user_ts;
    send_offset_i = true;
  }
  
  unsigned int rtmp_ts = (user_ts - send_rtmp_offset) 
    / (SPEEX_WB_SAMPLE_RATE/1000);
  packet.m_nTimeStamp  = rtmp_ts;

  RTMPPacket_Alloc(&packet,size+1);
  packet.m_nBodySize = size+1;

// soundType 	(byte & 0x01) » 0 	
//   0: mono, 1: stereo
// soundSize 	(byte & 0x02) » 1 	
//   0: 8-bit, 1: 16-bit
// soundRate 	(byte & 0x0C) » 2 	
//   0: 5.5 kHz, 1: 11 kHz, 2: 22 kHz, 3: 44 kHz
// soundFormat 	(byte & 0xf0) » 4 	
//   0: Uncompressed, 1: ADPCM, 2: MP3, 5: Nellymoser 8kHz mono, 6: Nellymoser, 11: Speex 


  // 0xB2: speex, 16kHz
  packet.m_body[0] = 0xB2;
  memcpy(packet.m_body+1,(unsigned char*)samples,size);

  //DBG("sending audio packet: size=%u rtmp_ts=%u StreamID=%u (rtp_ts=%u)\n",
  //    size+1,rtmp_ts,play_stream_id,user_ts);

  sender->push_back(packet);
  m_sender.unlock();

  //dump_audio(&packet);
  RTMPPacket_Free(&packet);

  return size;
}

void RtmpAudio::bufferPacket(const RTMPPacket& p)
{
  RTMPPacket np = p;
  if(!RTMPPacket_Alloc(&np,np.m_nBodySize)){
    ERROR("could not allocate packet.\n");
    return;
  }
  memcpy(np.m_body,p.m_body,p.m_nBodySize);

  m_q_recv.lock();
  q_recv.push(np);
  m_q_recv.unlock();
}

void RtmpAudio::process_recv_queue(unsigned int ref_ts)
{
  int size;

  // flush the recv queue into the playout buffer
  m_q_recv.lock();
  while(!q_recv.empty()){

    RTMPPacket p = q_recv.front();
    q_recv.pop();
    m_q_recv.unlock();

    //TODO:
    // - copy RTMP payload into this->samples
    // - decode
    // - put packet in playout buffer

    if(p.m_nBodySize <= (unsigned int)AUDIO_BUFFER_SIZE){
      

      size = p.m_nBodySize-1;
      memcpy((unsigned char*)samples, p.m_body+1, size);

      size = decode(size);
      if(size <= 0){
	ERROR("decode() returned %i\n",size);
	return;
      }
      
      // TODO: generate some reasonable RTP timestamp
      //
      bool begin_talk = false;
      if(!recv_offset_i){
	recv_rtp_offset  = ref_ts;
	recv_rtmp_offset = p.m_nTimeStamp;
	recv_offset_i = true;
	begin_talk = true;
      }

      unsigned int rtp_ts = (p.m_nTimeStamp - recv_rtmp_offset) * (SPEEX_WB_SAMPLE_RATE/1000);

      playout_buffer.write(ref_ts, rtp_ts, (ShortSample*)((unsigned char *)samples),
			   PCM16_B2S(size), begin_talk);
      
      RTMPPacket_Free(&p);    
    }

    m_q_recv.lock();
  }
  m_q_recv.unlock();
}

void RtmpAudio::add_to_history(int16_t *, unsigned int)
{
  return;
}

unsigned int RtmpAudio::conceal_loss(unsigned int, unsigned char *)
{
  return 0;
}

void RtmpAudio::setSenderPtr(RtmpSender* s)
{
  m_sender.lock();
  DBG("sender ptr = %p\n",s);
  sender = s;
  m_sender.unlock();
}

void RtmpAudio::setPlayStreamID(unsigned int stream_id)
{
  m_sender.lock();
  DBG("play_stream_id = %i\n",stream_id);
  play_stream_id = stream_id;
  m_sender.unlock();
}
