/*******************************************************************************
Copyright Datapath Ltd. 2015.

File:    Sample6A.cpp

History: 30 OCT 14    RL   Created.
         12 OCT 15    DC   Added further hard-coded URLs.
                           Included GOP Length as an option in URLs.

*******************************************************************************/

#include <liveMedia.hh>
#include <BasicUsageEnvironment.hh>
#include <GroupsockHelper.hh>

#include <string>

#include <rgb.h>
#include <rgbh264nal.h>
#include <stdio.h>
#include <tchar.h>

//#define _LOCAL_HEAP_DEBUG
#ifdef _LOCAL_HEAP_DEBUG
#include <CRTDBG.h>
#endif

/******************************************************************************/

static UsageEnvironment *_env = 0;
// Required for busy 1080p I frames
// #define MAX_PACKET_SIZE 1500000

#define MILLI_MS_SECOND 1000
#define MICRO_US_SECOND 1000000

class CRGBEasyH264FrameSource;

typedef struct _RGBEASYH264STREAM
{
   unsigned long           Input;
   uint32_t                Width;
   uint32_t                Height;
   uint32_t                FrameRate; // mHz
   DGCENCLEVEL             Level;
   DGCENCPROFILE           Profile;
   uint32_t                Bitrate; // bps
   uint32_t                KeyFrameInterval; // GOP Length
   CRGBEasyH264FrameSource *PFrameSourceVideo;
   ServerMediaSession      *PServerMediaSession;
} RGBEASYH264STREAM, *PRGBEASYH264STREAM;

struct
{
   uint32_t       Width;
   uint32_t       Height;
   uint32_t       FrameRate; // mHz
   DGCENCLEVEL    Level;
   DGCENCPROFILE  Profile;
   uint32_t       Bitrate; // bps
   uint32_t       KeyFrameInterval; // GOP Length
} Modes[] =
{
   { 640,  480, 15000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_HIGH,  50000000, 90},
   { 640,  480, 30000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_MAIN,  50000000, 90},
//   { 640,  480, 30000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_MAIN,  50000000, 60},
//   { 640,  480, 30000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_MAIN,  50000000, 45},
//   { 640,  480, 30000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_MAIN,  50000000, 30},
//   { 640,  480, 30000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_MAIN,  50000000, 15},
   { 640,  480, 60000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_HIGH,  62500000, 90},
   { 800,  600, 15000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_MAIN,  50000000, 90},
   { 800,  600, 30000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_MAIN,  50000000, 90},
//   { 800,  600, 30000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_MAIN,  50000000, 60},
//   { 800,  600, 30000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_MAIN,  50000000, 45},
//   { 800,  600, 30000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_MAIN,  50000000, 30},
//   { 800,  600, 30000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_MAIN,  50000000, 15},
   { 800,  600, 60000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_HIGH,  62500000, 90},
   {1024,  768, 15000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_MAIN,  50000000, 90},
   {1024,  768, 30000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_MAIN,  50000000, 90},
//   {1024,  768, 30000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_MAIN,  50000000, 60},
//   {1024,  768, 30000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_MAIN,  50000000, 45},
//   {1024,  768, 30000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_MAIN,  50000000, 30},
//   {1024,  768, 30000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_MAIN,  50000000, 15},
   {1024,  768, 60000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_HIGH,  62500000, 90},
   {1280,  720, 15000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_MAIN,  50000000, 90},
   {1280,  720, 30000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_MAIN,  50000000, 90},
//   {1280,  720, 30000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_MAIN,  50000000, 60},
//   {1280,  720, 30000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_MAIN,  50000000, 45},
//   {1280,  720, 30000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_MAIN,  50000000, 30},
//   {1280,  720, 30000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_MAIN,  50000000, 15},
   {1280,  720, 60000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_HIGH,  62500000, 90},
   {1280, 1024, 15000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_MAIN,  50000000, 90},
   {1280, 1024, 30000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_MAIN,  50000000, 90},
//   {1280, 1024, 30000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_MAIN,  50000000, 60},
//   {1280, 1024, 30000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_MAIN,  50000000, 30},
//   {1280, 1024, 30000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_MAIN,  50000000, 15},
   {1280, 1024, 60000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_HIGH,  62500000, 90},
   {1600, 1200, 15000, DGCENC_H264_LEVEL_5,   DGCENC_H264_PROFILE_MAIN,  50000000, 90},
   {1600, 1200, 30000, DGCENC_H264_LEVEL_5,   DGCENC_H264_PROFILE_MAIN,  50000000, 90},
   {1600, 1200, 60000, DGCENC_H264_LEVEL_5,   DGCENC_H264_PROFILE_HIGH,  62500000, 90},
   {1920, 1080, 15000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_MAIN,  50000000, 90},
   {1920, 1080, 30000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_MAIN,  50000000, 90},
//   {1920, 1080, 30000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_MAIN,  50000000, 60},
//   {1920, 1080, 30000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_MAIN,  50000000, 45},
//   {1920, 1080, 30000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_MAIN,  50000000, 30},
//   {1920, 1080, 30000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_MAIN,  50000000, 15},
   {1920, 1080, 60000, DGCENC_H264_LEVEL_4_2, DGCENC_H264_PROFILE_HIGH,  62500000, 90},
   {1920, 1200, 15000, DGCENC_H264_LEVEL_5,   DGCENC_H264_PROFILE_MAIN,  50000000, 90},
   {1920, 1200, 30000, DGCENC_H264_LEVEL_5,   DGCENC_H264_PROFILE_MAIN,  50000000, 90},
//   {1920, 1200, 30000, DGCENC_H264_LEVEL_5,   DGCENC_H264_PROFILE_MAIN,  50000000, 60},
//   {1920, 1200, 30000, DGCENC_H264_LEVEL_5,   DGCENC_H264_PROFILE_MAIN,  50000000, 45},
//   {1920, 1200, 30000, DGCENC_H264_LEVEL_5,   DGCENC_H264_PROFILE_MAIN,  50000000, 30},
//   {1920, 1200, 30000, DGCENC_H264_LEVEL_5,   DGCENC_H264_PROFILE_MAIN,  50000000, 15},
   {1920, 1200, 60000, DGCENC_H264_LEVEL_5,   DGCENC_H264_PROFILE_HIGH,  62500000, 90},
   // Add more modes to this list.
};
uint32_t numModes = sizeof( Modes ) / sizeof( Modes[0] );
char gStop = 0;

/******************************************************************************/
#if 0
char const* nal_unit_description_h264[32] = {
  "Unspecified", //0
  "Coded slice of a non-IDR picture", //1
  "Coded slice data partition A", //2
  "Coded slice data partition B", //3
  "Coded slice data partition C", //4
  "Coded slice of an IDR picture", //5
  "Supplemental enhancement information (SEI)", //6
  "Sequence parameter set", //7
  "Picture parameter set", //8
  "Access unit delimiter", //9
  "End of sequence", //10
  "End of stream", //11
  "Filler data", //12
  "Sequence parameter set extension", //13
  "Prefix NAL unit", //14
  "Subset sequence parameter set", //15
  "Reserved", //16
  "Reserved", //17
  "Reserved", //18
  "Coded slice of an auxiliary coded picture without partitioning", //19
  "Coded slice extension", //20
};
#endif
/******************************************************************************/

VOID NanoSecondsToTimeVal( uint64_t TimeStamp, timeval *pTV )
{
   uint64_t rem;

   pTV->tv_sec = (long)((uint64_t)(TimeStamp / 10000000));
   rem = TimeStamp % 10000000;
   pTV->tv_usec = (long)((uint64_t)(rem / 10));
}

VOID TimeValToNanoSeconds( timeval *pTV, uint64_t *pTimeStamp )
{
   *pTimeStamp = (((uint64_t) pTV->tv_sec)  * 10000000) +
                 (((uint64_t) pTV->tv_usec) * 10);
}

/******************************************************************************/

class CRGBEasyH264FrameSource : public FramedSource
{
   struct timeval fPresentationTimeEx;
   CRGBEasyH264 *m_pRGBEasyH264;
   void *m_pToken;
   uint32_t m_input;
   uint32_t m_error;
   uint64_t m_timeoffset;

public:
   CRGBEasyH264FrameSource (UsageEnvironment &env,
                             int input,
                             uint32_t width,
                             uint32_t height,
                             uint32_t frameRate,
                             DGCENCLEVEL level,
                             DGCENCPROFILE profile,
                             uint32_t bitrate,
                             uint32_t keyframeinterval ) : FramedSource( env ),
                             m_timeoffset( 0 )
	{
      uint32_t Error = 0;
		m_pToken = NULL;
      m_input = input;
      fPresentationTimeEx.tv_sec = 0;
      fPresentationTimeEx.tv_usec = 0;
      m_pRGBEasyH264 = new CRGBEasyH264 ( input );
      if((Error = m_pRGBEasyH264->RGBEasyH264Start ( width, height, frameRate,
                                        level, profile, bitrate,
                                        keyframeinterval )) == 0)
      {
         OutPacketBuffer::maxSize = (width * height * 3) / 2; // NV12 data format from encoder, assume no compression.
      }
      else
      {
         // We're in a constructor, so extraordinary measures are needed to fail this call.
         // Caller must wrap in a try{} block to discover the error, otherwise whole application unwinds and faults in main()
         delete m_pRGBEasyH264;
         throw Error;
      }
	}

	~CRGBEasyH264FrameSource ()
	{
      if ( m_pToken )
      {
			envir().taskScheduler().unscheduleDelayedTask(m_pToken);
		}
      m_pRGBEasyH264->RGBEasyH264Stop ( );
      delete m_pRGBEasyH264;
	}

protected:
   // Overridden from derived FrameSource class
	virtual void doGetNextFrame ()
	{
      RealGetNextFrame();
	}

private:
	static void getNextFrame (void *ptr)
	{
      // Static to run-time class adapter call.
		((CRGBEasyH264FrameSource*)ptr)->RealGetNextFrame();
	}

   // It would appear that it is incumbent upon this function to - at some point in the future - return a NAL
   // from the source.  In the case that there isn't already a NAL waiting on the queue, this function should
   // schedule a call to itself again, to make sure that the next delivered NAL is picked up in a timely
   // manner.
   // Inspection of the code doesn't show that this function actually returns a frame.  It returns a NAL.
   // Now, it could be that a NAL is also known as a frame, but to me a frame means "of video".  And given that
   // the reader process doesn't process NALs quickly enough such that over time the list willl grow and keep
   // growing, I'm wondering if there's a bug here?
	void RealGetNextFrame ()
   {
      static uint64_t lastts = 0;
      uint64_t timeStamp = 0;
      m_pToken = NULL;

      m_error = m_pRGBEasyH264->RGBEasyH264GetNAL ( fTo, fMaxSize, 
            &fNumTruncatedBytes, &fFrameSize, &timeStamp );

      if ( m_error == 0 )
      {
         // NAL returned from CRGBEasyH264 class.
         fDurationInMicroseconds = 0;
         if ( DoesH264NALUnitBeginNewAccessUnit(fTo) )
         {
            // If the NAL begins a new frame, we recalculate (and cache) the new frame presentation time
            // also, take the opportunity to cope with the timestamp values wrapping round.
            // It would also be good to check the magnitude of clock-drift here and adjust our time deltas
            // according to how much drift there is on the SQX clock.
            if (( m_timeoffset == 0 )|| (lastts > timeStamp))
            {
               struct timeval tvtimeOfDay;
               uint64_t timeOfDay;
               gettimeofday( &tvtimeOfDay, 0 );
               TimeValToNanoSeconds( &tvtimeOfDay, &timeOfDay );
               m_timeoffset = timeOfDay - timeStamp;
            }

            lastts = timeStamp;

            NanoSecondsToTimeVal( timeStamp + m_timeoffset, &fPresentationTimeEx );
            // TODO: use EndTime.
            fDurationInMicroseconds = 
               MICRO_US_SECOND / (m_pRGBEasyH264->m_StreamInfo.FPS / MILLI_MS_SECOND);
         }
         // fPresentationTimeEx is either the previous (frame beginning) NAL's presentation time, or the presentation
         // time for this new frame NAL.
         fPresentationTime = fPresentationTimeEx;
         afterGetting(this);
         // Something in the call stack sets up another scheduled event for the next NAL to be read.
         // However, this will cause the queue to grow uncontrollably as NALs may be added in pairs
         // which are only fetched (apparently) singly at the same rate.
         return;
      }
      else
      {
         // No NAL returned from CRGBEasyH264 class, wait a frame time and try again.
         int64_t microseconds;
         
         // Has CRGBEasyH264 class finished initialising with a signal attached?
         if ( m_pRGBEasyH264->m_StreamInfo.FPS )
         {
            microseconds = MICRO_US_SECOND / ( 
               m_pRGBEasyH264->m_StreamInfo.FPS / MILLI_MS_SECOND );
            // microseconds = 1;
         }
         else // wait 16.6ms
         {
            microseconds = MICRO_US_SECOND / ( 60000 / MILLI_MS_SECOND ); 
         }

         m_pToken = envir().taskScheduler().scheduleDelayedTask(
               microseconds, getNextFrame, this);
         return;     
	   }
   }
};

/******************************************************************************/

// A 'ServerMediaSubsession' object that creates a new, unicast, 
// "RTPSink"s on demand.
class myRTSPSinkOndemandMediaSubsession : public OnDemandServerMediaSubsession
{
public:
	// Static to run-time class adapter call.
   static myRTSPSinkOndemandMediaSubsession *createNew (
      UsageEnvironment &env, FramedSource *source, int input,
      uint32_t width,
      uint32_t height,
      uint32_t frameRate,
      DGCENCLEVEL level,
      DGCENCPROFILE profile,
      uint32_t bitrate,
      uint32_t keyframeinterval,
      TCHAR* streamName )
	{
		return new myRTSPSinkOndemandMediaSubsession(env, source, input,
                                                    width, height, frameRate,
                                                    level, profile, bitrate,
                                                    keyframeinterval, streamName );
	}

protected:
   // This will create only one source that will be shared by all the sinks.
	myRTSPSinkOndemandMediaSubsession (
         UsageEnvironment &env,
         FramedSource *pFrameSource,
         int input,
         uint32_t width,
         uint32_t height,
         uint32_t frameRate,
         DGCENCLEVEL level,
         DGCENCPROFILE profile,
         uint32_t bitrate,
         uint32_t keyframeinterval,
         TCHAR* streamName)
         : OnDemandServerMediaSubsession(env, True)
	{
		m_pFrameSource = pFrameSource;
		m_pSDP = NULL;
      m_input = input;
      m_width = width;
      m_height = height;
      m_frameRate = frameRate;
      m_level = level;
      m_profile = profile;
      m_bitrate = bitrate;
      m_keyframeinterval = keyframeinterval;
      m_streamName = streamName;
	}

	~myRTSPSinkOndemandMediaSubsession ()
	{
		if (m_pSDP) 
         free(m_pSDP);
	}

private:
	static void afterPlayingDummy (void *ptr)
	{
		myRTSPSinkOndemandMediaSubsession *This = 
            (myRTSPSinkOndemandMediaSubsession*)ptr;
		This->m_done = 127;
	}

	static void chkForAuxSDPLine (void *ptr)
	{
		myRTSPSinkOndemandMediaSubsession *This = 
            (myRTSPSinkOndemandMediaSubsession *)ptr;
		This->RealchkForAuxSDPLine();
	}

	void RealchkForAuxSDPLine ()
	{
      *_env << "chkForAuxSDPLine: " <<  m_streamName.data() << "\n";
      if (m_pDummy_RTPSink->auxSDPLine())
      {
         m_done = 127;
         m_pSDP = _strdup(m_pDummy_RTPSink->auxSDPLine());
         m_pDummy_RTPSink->stopPlaying();
      }
      else 
      {
         int delay = MILLI_MS_SECOND*100;
         nextTask() = envir().taskScheduler().scheduleDelayedTask(
               delay, chkForAuxSDPLine, this);
      }
	}

protected:

	virtual const char *getAuxSDPLine (RTPSink *sink, FramedSource *source)
	{
		if (m_pSDP) 
         return m_pSDP;

		m_pDummy_RTPSink = sink;
		m_pDummy_RTPSink->startPlaying(*source, 0, 0);
		chkForAuxSDPLine(this);
		m_done = 0;
		envir().taskScheduler().doEventLoop(&m_done);

		return m_pSDP;
	}

	virtual RTPSink *createNewRTPSink(
         Groupsock *rtpsock, uint_least8_t type, FramedSource *source)
	{
		return H264VideoRTPSink::createNew(envir(), rtpsock, type);
	}

	virtual FramedSource *createNewStreamSource (
         unsigned sid, unsigned &estBitrate)
	{
		estBitrate = m_bitrate; // Was MAX_PACKET_SIZE, which we've dispensed with
      try {
		   return H264VideoStreamDiscreteFramer::createNew(
               envir(), new CRGBEasyH264FrameSource(envir(), m_input,
               m_width, m_height, m_frameRate, m_level, m_profile, m_bitrate,
               m_keyframeinterval ) );
      }
      catch (uint32_t e)
      {
         // The encoder couldn't be opened, or couldn't start.
         printf("Cannot start RGBEasy encoder (input already open elsewhere?) - Error: 0x%x\n", e);
         return NULL;
      }
	}

private:
	FramedSource *m_pFrameSource;
	int_fast8_t *m_pSDP;
	RTPSink *m_pDummy_RTPSink;
	int_fast8_t m_done;
   uint32_t m_input;
   uint32_t m_width;
   uint32_t m_height;
   uint32_t m_frameRate;
   DGCENCLEVEL m_level;
   DGCENCPROFILE m_profile;
   uint32_t m_bitrate;
   uint32_t m_keyframeinterval;
   std::string m_streamName;
};

/******************************************************************************/

ServerMediaSession * createServerMediaSession( UsageEnvironment &env,
                                               FramedSource *source,
                                               uint32_t input,
                                               uint32_t width,
                                               uint32_t height,
                                               uint32_t frameRate,
                                               DGCENCLEVEL level,
                                               DGCENCPROFILE profile,
                                               uint32_t bitrate,
                                               uint32_t keyframeinterval )
{
   ServerMediaSession *p;
   TCHAR streamName[255], description[255];

   wsprintf( streamName, TEXT( "input%d?resolution=%dx%d&rate=%d" ),
             input + 1, width, height, frameRate, keyframeinterval );
   wsprintf( description, TEXT( "RGBEasy H264 Unicast Session %s" ), streamName );

   p = ServerMediaSession::createNew( env, streamName, 0, description );

   // Insert into the media session a video stream.
   p->addSubsession(
      myRTSPSinkOndemandMediaSubsession::createNew( env, source, input,
         width, height, frameRate, level, profile, bitrate, keyframeinterval, streamName ) );

   // Insert into the media session an audio stream.
   //p->addSubsession(
   //   myRTSPSinkOndemandMediaSubsession::createNew( env, source, input ) );

   return p;
}

/******************************************************************************/

BOOL WINAPI HandlerRoutine( DWORD dwCtrlType )
{
   gStop = 1;
   return TRUE;
}

/******************************************************************************/

int main( int argc, TCHAR **argv )
{
   uint32_t inputCount, h264Count, i;
   uint32_t *pInputList;

   TaskScheduler *pScheduler = BasicTaskScheduler::createNew();
   _env = BasicUsageEnvironment::createNew(*pScheduler);

#ifdef _LOCAL_HEAP_DEBUG
   {
      int tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG );

      // Turn on leak-checking bit.
      tmpFlag |= _CRTDBG_LEAK_CHECK_DF;

      // Turn off CRT block checking bit.
      tmpFlag &= ~_CRTDBG_CHECK_CRT_DF;

      // Set flag to the new value.
      _CrtSetDbgFlag( tmpFlag );
   }
#endif

   if ( GetSupportedH264Inputs (&pInputList, &inputCount, &h264Count) == 0 )
   {   
#ifdef ACCESS_CONTROL
     UserAuthenticationDatabase* authDB = NULL;
     // To implement client access control to the RTSP server, do the following:
     authDB = new UserAuthenticationDatabase;
     authDB->addUserRecord("username1", "password1"); // replace these with real strings
     // Repeat the above with each <username>, <password> that you wish to allow
     // access to the server.
#endif

      // Create the RTSP server.  Try first with the default port number (554),
      // and then with any alternative port numbers
      RTSPServer* rtspServer = NULL;
      uint32_t port=554;
      do 
      {
         rtspServer = RTSPServer::createNew(*_env, port /*authDB*/);
         if(port==554) port=8553;
         port++;
      } while(rtspServer == NULL && port<9555);

	   if (rtspServer) 
      {
         // Set up each of the possible streams that can be served by the
         // RTSP server.  Each such stream is implemented using a
         // "ServerMediaSession" object, plus one or more
         // "ServerMediaSubsession" objects for each audio/video substream.
         PRGBEASYH264STREAM pStream = (PRGBEASYH264STREAM)malloc (
            sizeof(RGBEASYH264STREAM)*h264Count*numModes);
         if ( pStream )
         {
            unsigned long j =0;

            for ( i=0; i< inputCount; i++)
            {
               if ( pInputList[i] ) // if input supports h264
               {
                  unsigned long k;
                  for ( k = 0; k < numModes; k++ )
                  {
                     pStream[j].Input = i;
                     pStream[j].Width = Modes[k].Width;
                     pStream[j].Height = Modes[k].Height;
                     pStream[j].FrameRate = Modes[k].FrameRate;
                     pStream[j].Level = Modes[k].Level;
                     pStream[j].Profile = Modes[k].Profile;
                     pStream[j].Bitrate = Modes[k].Bitrate;
                     pStream[j].KeyFrameInterval = Modes[k].KeyFrameInterval;
                     // Specify a unicast, reuse same source RTP sink class handler.
                     pStream[j].PFrameSourceVideo = NULL;
                     // Create and add the description of a media session.

                     pStream[j].PServerMediaSession = createServerMediaSession(
                        *_env, pStream[j].PFrameSourceVideo, pStream[j].Input,
                        pStream[j].Width, pStream[j].Height, pStream[j].FrameRate,
                        pStream[j].Level, pStream[j].Profile, pStream[j].Bitrate,
                        pStream[j].KeyFrameInterval);
                     if ( pStream[j].PServerMediaSession )
                     {
                        // Add the media session to the RTSP server
                        rtspServer->addServerMediaSession(pStream[j].PServerMediaSession);
                        *_env << "using url \"" << rtspServer->rtspURL(
                           pStream[j].PServerMediaSession) << "\"\n";
                     }
                     else
                        *_env << "ERROR - creating media session\n";
                     j++;
                  }
                  *_env << "\n";
               }
            }

            // Add a CTRL-C handler routine.
            SetConsoleCtrlHandler( HandlerRoutine, TRUE );
            // run loop
            gStop = 0;
	         _env->taskScheduler().doEventLoop( &gStop );
            free (pStream);
         }
      }
      else
         *_env << "ERROR - creating RTSPServer\n";

      free( pInputList );
   }
   else
      *_env << "ERROR - No H264 inputs supported\n";

#ifdef _LOCAL_HEAP_DEBUG
   _CrtDumpMemoryLeaks( );
#endif

   Sleep(3000);

   return 0;
}

/******************************************************************************/
