/**********
This library is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2.1 of the License, or (at your
option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)

This library 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 Lesser General Public License for
more details.

You should have received a copy of the GNU Lesser General Public License
along with this library; if not, write to the Free Software Foundation, Inc.,
59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
**********/
// Copyright (c) 1996-2000, Live Networks, Inc.  All rights reserved
// A test program that receives a RTP/RTCP multicast MPEG video stream,
// and outputs the resulting MPEG file stream to 'stdout'
// main program

#include "liveMedia.hh"
#include "GroupsockHelper.hh"
#include "BasicUsageEnvironment.hh"
#include "OutputFile.hh"

// To receive a "source-specific multicast" (SSM) stream, uncomment this:
//#define USE_SSM 1

char *mstrtime( char *psz_buffer, int64_t date );
void afterPlaying(void* clientData); // forward

static void FrameRead( void *clientData, unsigned int frameSize,
                        unsigned int numTruncatedBytes, struct timeval presentationTime,
                        unsigned int durationInMicroseconds );

static void FrameClose( void *clientData );


// A structure to hold the state of the current session.
// It is used in the "afterPlaying()" function to clean up the session.
struct sessionState_t {

  char*          stopScheduler;
  unsigned int   read;
  unsigned int   rtcpSync;
  unsigned char* buffer;
  unsigned int   bufSize;
  RTPSource*     source;
  RTCPInstance*  rtcpInstance;

} SessionState[2];

UsageEnvironment* env;
FILE* logFile;

int main(int argc, char** argv) {
  // Begin by setting up our usage environment:
  TaskScheduler* scheduler = BasicTaskScheduler::createNew();
  env = BasicUsageEnvironment::createNew(*scheduler);

  logFile = OpenOutputFile(*env, "LogFile.txt" );
  if (logFile == NULL)
    exit(1);

  char event = false;
  SessionState[0].stopScheduler = &event;
  SessionState[0].bufSize = 8192;
  SessionState[0].buffer = new unsigned char [SessionState[0].bufSize];
  SessionState[0].read = True;
  SessionState[0].rtcpSync = False;

  SessionState[1].stopScheduler = &event;
  SessionState[1].bufSize = 8192;
  SessionState[1].buffer = new unsigned char [SessionState[1].bufSize];
  SessionState[1].read = True;
  SessionState[1].rtcpSync = False;

  // Create 'groupsocks' for RTP and RTCP:
  char* sessionAddressStr
#ifdef USE_SSM
    = "232.255.42.42";
#else
    = "239.255.42.42";
  // Note: If the session is unicast rather than multicast,
  // then replace this string with "0.0.0.0"
#endif
  const unsigned short rtpPortNumVideo  = 8888;
  const unsigned short rtcpPortNumVideo = rtpPortNumVideo+1;
  const unsigned short rtpPortNumAudio  = 6666;
  const unsigned short rtcpPortNumAudio = rtpPortNumAudio+1;
#ifndef USE_SSM
  const unsigned char ttl = 1; // low, in case routers don't admin scope
#endif
  
  struct in_addr sessionAddress;
  sessionAddress.s_addr = our_inet_addr(sessionAddressStr);
  const Port rtpPortVideo(rtpPortNumVideo);
  const Port rtcpPortVideo(rtcpPortNumVideo);
  const Port rtpPortAudio(rtpPortNumAudio);
  const Port rtcpPortAudio(rtcpPortNumAudio);
  
#ifdef USE_SSM
  char* sourceAddressStr = "aaa.bbb.ccc.ddd";
                           // replace this with the real source address
  struct in_addr sourceFilterAddress;
  sourceFilterAddress.s_addr = our_inet_addr(sourceAddressStr);

  Groupsock rtpGroupsockVideo(*env, sessionAddress, sourceFilterAddress, rtpPortVideo);
  Groupsock rtcpGroupsockVideo(*env, sessionAddress, sourceFilterAddress, rtcpPortVideo);
  Groupsock rtpGroupsockAudio(*env, sessionAddress, sourceFilterAddress, rtpPortAudio);
  Groupsock rtcpGroupsockAudio(*env, sessionAddress, sourceFilterAddress, rtcpPortAudio);
  rtcpGroupsockVideo.changeDestinationParameters(sourceFilterAddress,0,~0);
  rtcpGroupsockAudio.changeDestinationParameters(sourceFilterAddress,0,~0);
      // our RTCP "RR"s are sent back using unicast
#else
  Groupsock rtpGroupsockVideo(*env, sessionAddress, rtpPortVideo, ttl);
  Groupsock rtcpGroupsockVideo(*env, sessionAddress, rtcpPortVideo, ttl);
  Groupsock rtpGroupsockAudio(*env, sessionAddress, rtpPortAudio, ttl);
  Groupsock rtcpGroupsockAudio(*env, sessionAddress, rtcpPortAudio, ttl);
#endif
  
  // Create the video and audio data source
  // BIG WARNING: hardcoded to a 48K PCM 2 channels audio
  SessionState[0].source = MPEG1or2VideoRTPSource::createNew(*env, &rtpGroupsockVideo);
  SessionState[1].source = SimpleRTPSource::createNew(*env, &rtpGroupsockAudio, 96,
				                                           48000, "audio/L16",0,0);

  // Create (and start) a 'RTCP instance' for the RTP source:
  const unsigned estimatedSessionBandwidth = 160; // in kbps; for RTCP b/w share
  const unsigned maxCNAMElen = 100;
  unsigned char CNAME[maxCNAMElen+1];
  gethostname((char*)CNAME, maxCNAMElen);
  CNAME[maxCNAMElen] = '\0'; // just in case
  SessionState[0].rtcpInstance = RTCPInstance::createNew(*env, &rtcpGroupsockVideo,
			                              estimatedSessionBandwidth, CNAME,
			                              NULL /* we're a client */, SessionState[0].source);

  SessionState[1].rtcpInstance = RTCPInstance::createNew(*env, &rtcpGroupsockAudio,
			                              estimatedSessionBandwidth, CNAME,
			                              NULL /* we're a client */, SessionState[1].source);
  // Note: This starts RTCP running automatically

  // Finally, start receiving the multicast stream:
  *env << "Beginning receiving multicast stream...without sinking....\n";

  // Forever... But who care...testing case...
  while (1) {
     sessionState_t * pSessionState;
     for (int i=0; i < 2; i++) {
        pSessionState = &SessionState[i];
        while (pSessionState->read == True) {

           pSessionState->read = False;
           pSessionState->source->getNextFrame( pSessionState->buffer, pSessionState->bufSize,
                                              FrameRead, pSessionState, 
                                              FrameClose, pSessionState );
        }
     }
  event = false;
  env->taskScheduler().doEventLoop(&event);
  }

  return 0; // only to prevent compiler warning
}


void afterPlaying(void* /*clientData*/) {
  *env << "...done receiving\n";

  // End by closing the media:
  Medium::close(SessionState[0].rtcpInstance); // Note: Sends a RTCP BYE
  Medium::close(SessionState[1].rtcpInstance); // Note: Sends a RTCP BYE
  Medium::close(SessionState[0].source);
  Medium::close(SessionState[1].source);

  delete SessionState[0].buffer;
  delete SessionState[1].buffer;
}


static void FrameRead( void *clientData, unsigned int frameSize,
                        unsigned int numTruncatedBytes, struct timeval presentationTime,
                        unsigned int durationInMicroseconds ) {
  sessionState_t * pSessionState = (sessionState_t *) clientData;

  char  cbuf[32];
  int64_t date = presentationTime.tv_sec*1000000 + presentationTime.tv_usec; 
  date &= 0x7fffffffffffffff; // Dont want negative math

  if ( pSessionState == &SessionState[0]) {
     if (pSessionState->source->hasBeenSynchronizedUsingRTCP() && pSessionState->rtcpSync == False) {
        fprintf( logFile, "\nVideo hasBeenSynchronizedUsingRTCP()\n\n", frameSize, mstrtime(cbuf, date) );
        pSessionState->rtcpSync = True;
     }
     fprintf( logFile, "Video received %4d bytes at time %s\n", frameSize, mstrtime(cbuf, date) );
  }
  else {
     if (pSessionState->source->hasBeenSynchronizedUsingRTCP() && pSessionState->rtcpSync == False) {
        fprintf( logFile, "\nAudio hasBeenSynchronizedUsingRTCP()\n\n", frameSize, mstrtime(cbuf, date) );
        pSessionState->rtcpSync = True;
     }
    fprintf( logFile, "Audio received %4d bytes at time %s\n", frameSize, mstrtime(cbuf, date) );
  }

  *pSessionState->stopScheduler = True;
   pSessionState->read = True;
}


static void FrameClose( void *clientData ){
}

//
// code inspired from VLC under GNU.
//

char *mstrtime( char *psz_buffer, int64_t date )
{
    static int64_t ll1000 = 1000, ll60 = 60, ll24 = 24, ll365 = 365;

    sprintf( psz_buffer, "%d-%02d:%02d:%02d-%03d.%03d",
             (int) (date / (ll1000 * ll1000 * ll60 * ll60 * ll24) % ll365),
             (int) (date / (ll1000 * ll1000 * ll60 * ll60) % ll24),
             (int) (date / (ll1000 * ll1000 * ll60) % ll60),
             (int) (date / (ll1000 * ll1000) % ll60),
             (int) (date / ll1000 % ll1000),
             (int) (date % ll1000) );
    return( psz_buffer );
}
