
// Dialogic One Event - Adding Video Lab

// Exercise 4 Solution
//
// End with:
//      - Full mutlimedia application
//
// This version will take calls, and audio/video menus and clips will work


// Standard include files
#include <stdafx.h>
#include <signal.h>
#include <time.h>
#include <sys/timeb.h>
#include <errno.h>
#include <fcntl.h>

// Standard Dialogic R4 and GlobalCall Includes
#include <srllib.h>
#include <gclib.h>
#include <gcip.h>
#include <gcip_defs.h>

// SDP API code and built library will be needed
#include <sdpAPI.h>

// IPML (RTP streaming) include file
#include <ipmlib.h>

// Device management and Multimedia include files
#include <devmgmt.h>
#include <mmlib.h>
#include <mmevts.h>

// Some GC sizing for this demo
// We are using the 4 port eval license
#define TOTAL_MAX_CALLS		4
#define SIP_MAX_CALLS		4

// Application events
// Note: these must be out of ramge of any possible SRL events
#define APP_MENU_1			0x01
#define APP_MENU_2			0x02

// Application defines
#define PLAY_MENU			0x01
#define PLAY_CLIP1			0x02
#define PLAY_CLIP2			0x03

// General Globals
long			SIPDevHandle, mediaDevHandle, voiceDevHandle, dtmfDevHandle, videoDevHandle;
char			errStr[256];
bool			appRunning = true;
bool			mediaStreaming = false;
bool			isGetDigitPending = false;
DV_DIGIT        DTMFdigit;
unsigned int    LocalAudioPort  = 0;
unsigned int    LocalVideoPort  = 0;
char            LocalIPAddr[32];
char            RemoteIPAddr[32];
unsigned int    RemoteAudioPort = 0;
unsigned int    RemoteVideoPort = 0;
bool			useVideo;  // Is this a video call request?
bool			hangupWhenDone = false;
bool			filePlayInProgress = false;
CRN				curCallCRN;
FILE			*logfileHandle = NULL;

// Globals for async file play
DX_IOTT iott[1];
DV_TPT  tpt[1];
int fhandle;

// Catch keyboard interupts for clean exit
void intr_handler(int h)
{
    appRunning = false;
}

//********************************* Logging ******************************

enum ReportType {
    INFO_MSG        = 1,
    INFO_ERROR      = 2,
    ERROR_GC        = 3
};


void LogEvent(int iType, char *fmt, ...)
{
    // Local Variable Declaration
    char    sTemp[1024];
    char    *sCcMsg;
    char    *sLibName;
    int     iCcLibId;
    int     iGcError;
    long    iCcError;
    va_list args;
    int     iPos = 0;


    struct  _timeb timebuffer;
    char    *timeline;

    _ftime(&timebuffer);
    timeline = ctime(&(timebuffer.time));
    iPos += sprintf(sTemp + iPos, "[%.15s.%03hu] ", &timeline[4], timebuffer.millitm);
    switch (iType) {
    case INFO_MSG:
        iPos += sprintf(sTemp + iPos, "[INFO]");
        break;

    case INFO_ERROR:
        iPos += sprintf(sTemp + iPos, "[ERROR]");
        //fprintf(stdout, sTemp);	
        break;

    case ERROR_GC:
        LogEvent(INFO_MSG, "Getting GC Error Info.");
        if (gc_ErrorValue(&iGcError, &iCcLibId, &iCcError) < 0) {
            // error getting error!
            LogEvent(INFO_ERROR, "Error - gc_ErrorValue");
        }
        if (gc_ResultMsg(iCcLibId, iCcError, &sCcMsg) < 0) {
            LogEvent(INFO_ERROR, "Error - gc_ResultMsg (CcLibId)");
        }
        if (gc_CCLibIDToName(iCcLibId, &sLibName) < 0) {
            LogEvent(INFO_ERROR, "Error - gc_CCLibIDToName");
        }
        iPos += sprintf(sTemp + iPos, "\t%s ERROR: 0x%X: %s",
                        sLibName,
                        iCcError,
                        sCcMsg);
        break;

    default:
        iPos += sprintf(sTemp + iPos, "[INFO TYPE NOT DEFINED]");           
        break;
    }

    iPos += sprintf(sTemp + iPos, " -> ");
    va_start(args, fmt);
    iPos += vsprintf(sTemp + iPos, fmt, args);
    va_end(args);

    // Send message to stdout and log file
    int nLen = strlen(sTemp);
    strcpy(sTemp + nLen, "\n");         
    fprintf(stdout, sTemp); 
	if (logfileHandle)
		fprintf(logfileHandle, sTemp); 
}

void OpenLogFile ()
{
	logfileHandle = fopen("./AddingVideoLab.log", "w+");
	if (logfileHandle == NULL)
		LogEvent(INFO_ERROR, "Not able to open log file");
	else
		LogEvent (INFO_MSG, "Log file opened");
}

void CloseLogFile ()
{
	fclose(logfileHandle);
}

// ************************************* End Logging **************************************

// Forward Declarations
void processGCEvent(METAEVENT metaevent);
void processOtherEvent(long evtDev, long evtType, void *pEvtData, long evtLen);
void StopDTMF();
void StopMMPlay();


void StopMedia();

//************************************ GC & 3PCC ****************************************

void HangUp ()
{
	LogEvent(INFO_MSG, "Dropping call");

    // Stop any multimedia activity
    StopMMPlay();

	// Stop DTMF audio device
	StopDTMF();

    // Stop RTP streaming
    StopMedia();

	if (gc_DropCall(curCallCRN, GC_NORMAL_CLEARING, EV_ASYNC) == -1) {
		LogEvent(ERROR_GC, "Error dropping call");
	}
}

void ParseOfferSDP(char * parmData, bool *addVideo)
{
    // Parse out the pieces of the incoming SDP with the sdpapi.
    // Log them to illustrate what's being found.
    // Save address, ports, flags needed for starting the media stream(s)

	// Import the SDP from GC into the sdpapi for parsing
	sdpSessionDescription sdp;
    sdp.clear();
    //2nd param is for debugging (true/false) 
    sdp.importSDP(parmData, true);


    LogEvent(INFO_MSG, "SDP connection = %s", sdp.connection()->getValue());
    strcpy(RemoteIPAddr, sdp.connection()->getAddress());
    LogEvent(INFO_MSG, "SDP origin = %s", sdp.origin()->getValue());
    LogEvent(INFO_MSG, "SDP session name = %s",sdp.sessionName()->getName());

    int NumMD = sdp.mediaDescriptionList()->numItem();
    LogEvent(INFO_MSG, "SDP contains %d media descriptor(s)",NumMD);
	*addVideo = false;

    int i,j;
    for (i=0;i<NumMD;i++) {
        sdpMediaDescription* pMD = sdp.mediaDescriptionList()->getItem(i);
        sdpMedia* pMedia = pMD->media();
        int NumMedia = pMedia->getNumFormat();
        LogEvent(INFO_MSG, "Media descriptor #%d = %s",i+1,pMedia->getValue());
        LogEvent(INFO_MSG, "Media descriptor #%d contains %d media format(s)",i+1,NumMedia);
        for (j=0;j<NumMedia;j++) {
            LogEvent(INFO_MSG, "Media format #%d = %s",j+1,pMedia->getFormat(j));

            // Looking for G.711 Nulaw
            if (!strcmp(pMedia->getFormat(j),"0")) {
                // G.711 MuLaw Audio is supported
                // Need to handle case where it's not available
                LogEvent(INFO_MSG, "G.711 MuLaw Audio is supported");
                RemoteAudioPort = pMD->media()->getPort();
            }
            // Looking for H.263
            if (!strcmp(pMedia->getFormat(j),"34")) {
                RemoteVideoPort = pMD->media()->getPort();
                LogEvent(INFO_MSG, "H.263 video is supported");
                *addVideo = true;
            }
            // You might also want to look for RFC2833 here, and set up DTMF accordingly
			// We're just assuming 2833 for this exercise
        }

		// Informational only - just to see what else there is to parse
        sdpAttributeList* pAttributeList = pMD->attributeList();
        int NumAttribute = pAttributeList->numItem();
        LogEvent(INFO_MSG, "Media descriptor #%d contains %d attribute(s)",i+1,NumAttribute);
        for (j=0;j<NumAttribute;j++) {
            sdpAttribute* pAttribute = pAttributeList->getItem(j);
            LogEvent(INFO_MSG, "Attribute #%d = %s",j+1, pAttribute->getValue());
        }
    }
}


void BuildAnswerSDP(sdpSessionDescription *sdp, bool addVideoMedia)
{
    // Use the sdpapi to build an SDP from scratch
    // Local IP adress and audio/video ports have already been found
    // and stored using an IPML GetMedia call

    // Main choice when building SDP is audio only or audio/video

    sdp->version()->setVersion("0");

    sdp->origin()->setUserName("AddingVideoLab");
    sdp->origin()->setSessionId("1234");
    sdp->origin()->setVersion("5678");
    sdp->origin()->setNetworkType("IN");
    sdp->origin()->setAddressType("IP4");
    sdp->origin()->setAddress(LocalIPAddr);

    sdp->sessionName()->setName("Intel_SIP_CCLIB");
    sdp->sessionInformation()->setInfo("AddingVideoLab Session Info");

    sdp->connection()->setNetworkType("IN");
    sdp->connection()->setAddressType("IP4");
    sdp->connection()->setAddress(LocalIPAddr);

    sdpTimeDescription* pTD=sdp->timeDescriptionList()->addItem();
    unsigned long Start = 0;
    unsigned long Stop = 0;
    pTD->time()->setStart(Start);
    pTD->time()->setStop(Stop);

    sdpAttribute* pAttribute = sdp->attributeList()->addItem();
    sdpMediaDescription* pMD=sdp->mediaDescriptionList()->addItem();

    pMD->media()->setMedia("audio");
    pMD->media()->setPort(LocalAudioPort);
    pMD->media()->setTransport("RTP/AVP");
    pMD->media()->setNumPorts(1);
    pMD->media()->addFormat("0");
    pMD->media()->addFormat("101");

    sdpAttributeList* pAttributeList = pMD->attributeList();
    pAttribute = pAttributeList->addItem();
    pAttribute->setProperty("rtpmap");
    pAttribute->setPropertyValue("0 PCMU/8000");

    pAttribute = pAttributeList->addItem();
    pAttribute->setProperty("rtpmap");
    pAttribute->setPropertyValue("101 telephone-event/8000");

    if (addVideoMedia) {
        // Video requested, so add another media type and attributes
        pMD=sdp->mediaDescriptionList()->addItem();

        pMD->media()->setMedia("video");
        pMD->media()->setPort(LocalVideoPort);
        pMD->media()->setTransport("RTP/AVP");
        pMD->media()->setNumPorts(1);
        pMD->media()->addFormat("34");

        pAttributeList = pMD->attributeList();
        pAttribute = pAttributeList->addItem();
        pAttribute->setProperty("rtpmap");
        pAttribute->setPropertyValue("34 H263/90000");

        pAttribute = pAttributeList->addItem();
        pAttribute->setProperty("fmtp");
        pAttribute->setPropertyValue("34 CIF=1 QCIF=1");
    }
}

//************************************* End GC & 3PCC *******************************

// ******************************** IPML ************************************

void StopMedia ()
{
    if (mediaStreaming) {
        // Stop IPM streaming if necessary
        LogEvent(INFO_MSG, "Stopping IPM media device");
        if (ipm_Stop(mediaDevHandle, STOP_ALL, EV_SYNC) != 0) {
            LogEvent(INFO_ERROR, "ipm_Stop() on device %d Failed - %s", mediaDevHandle, ATDV_ERRMSGP(mediaDevHandle));
        }
        mediaStreaming = false;
    }
}


void MonitorIPMforDTMF()
{
    // Get timeslots for IPM device
    SC_TSINFO ip_tsinfo;
    long ip_scts;

    ip_tsinfo.sc_numts = 1;
    ip_tsinfo.sc_tsarrayp = &ip_scts;
    if (ipm_GetXmitSlot(mediaDevHandle, &ip_tsinfo, EV_SYNC) == -1) {
        LogEvent(INFO_ERROR, "Error getting timeslot for IPM device");
    } else {
        LogEvent(INFO_MSG, "Timeslot for ipm  device = %d", ip_tsinfo.sc_tsarrayp[0]);
    }

    // Listen to IPM timeslot with DTMF voice device
    if (dx_listen(dtmfDevHandle, &ip_tsinfo) == -1) {
        LogEvent(INFO_ERROR, "Error on voice device listen");
    } else {
        LogEvent(INFO_MSG, "DTMF device listening to IPM timeslot %d", ip_tsinfo.sc_tsarrayp[0]);
    }
}

void UnMonitorIPMfromDTMF()
{
    if (dx_unlisten(dtmfDevHandle) == -1) {
        LogEvent(INFO_ERROR, "Error on DTMF device unlisten");
    } else {
        LogEvent(INFO_MSG, "DTMF device unlisten successful");
    }
}

void ConnectIPMtoMM()
{
    LogEvent(INFO_MSG, "Connecting IPM and MM devices");
    // Note: Only async mode is supported with devConnect.
    if (dev_Connect(mediaDevHandle, videoDevHandle, DM_FULLDUP, EV_ASYNC) < 0) {
        LogEvent(INFO_ERROR, "dev_Connect() failed");
    }
}

void DisconnectIPMfromMM()
{
    LogEvent(INFO_MSG, "Disconnecting IPM from MM devices");
    // Note: Only async mode is supported with devConnect.
    if (dev_Disconnect(mediaDevHandle, EV_ASYNC) < 0) {
        LogEvent(INFO_ERROR, "dev_Connect() failed");
    }
}

void GetLocalIPMInfo ()
{
    LogEvent(INFO_MSG, "Fetching local IPM information");

    // Set up ip media struct to return both audio and video info
	// Needed when setting up for IPML (RTP) streaming
    IPM_MEDIA_INFO ipmLocalMedia;
    ipmLocalMedia.unCount = 2;
    ipmLocalMedia.MediaData[0].eMediaType = MEDIATYPE_AUDIO_LOCAL_RTP_INFO;
    ipmLocalMedia.MediaData[1].eMediaType = MEDIATYPE_VIDEO_LOCAL_RTP_INFO;

    if (ipm_GetLocalMediaInfo(mediaDevHandle, &ipmLocalMedia, EV_ASYNC) < 0) {
        LogEvent(INFO_ERROR, "ipm_GetLocalMediaInfo() failed on %s", mediaDevHandle);
    }
}


void SaveLocalIPMInfo (void *pData)
{
    // Extract audio/video IP address and ports out of the struct now,
    // and save

    LogEvent(INFO_MSG, "Saving local IPM information");

    IPM_MEDIA_INFO ipmLocalMedia = *(IPM_MEDIA_INFO *)pData;
    for (unsigned int i = 0; i < ipmLocalMedia.unCount; i++) {
        switch (ipmLocalMedia.MediaData[i].eMediaType) {
        case MEDIATYPE_AUDIO_LOCAL_RTP_INFO:
            LocalAudioPort = ipmLocalMedia.MediaData[i].mediaInfo.PortInfo.unPortId;
            strcpy(LocalIPAddr, ipmLocalMedia.MediaData[i].mediaInfo.PortInfo.cIPAddress);
            break;
        case MEDIATYPE_VIDEO_LOCAL_RTP_INFO:
            LocalVideoPort = ipmLocalMedia.MediaData[i].mediaInfo.PortInfo.unPortId;
            break;
        case MEDIATYPE_REMOTE_RTP_INFO:
            break;
        case MEDIATYPE_REMOTE_RTCP_INFO:
            break;
        case MEDIATYPE_LOCAL_RTCP_INFO:
            break;
        case MEDIATYPE_REMOTE_CODER_INFO:
            break;
        case MEDIATYPE_LOCAL_CODER_INFO:
            break;
        default:
            break;
        }
    }
    LogEvent(INFO_MSG, "localAudioPort = %d, localVideoPort = %d, localIPAddr = %s\n", 
             LocalAudioPort, LocalVideoPort, LocalIPAddr);
}

void SetDtmfModeRFC2833()
{
    // Sets the DTMF mode in IPML to RFC2833
	// A similar, but simpler procedure sets it to inband DTMF

    IPM_PARM_INFO       parmInfo;
    eIPM_DTMFXFERMODE   value;
    int                 PLType  = 0;
    
    value = DTMFXFERMODE_RFC2833;
    parmInfo.eParm  = PARMCH_DTMFXFERMODE;
    parmInfo.pvParmValue = &value;
    LogEvent(INFO_MSG, "DTMFs will be detected using RFC2833");
    if (ipm_SetParm(mediaDevHandle, &parmInfo, EV_SYNC) < 0) {
        LogEvent(INFO_ERROR, "ipm_SetParm() error RFC2833");
    }

    // set the TX Payload Type
    PLType                  = 101;
    parmInfo.eParm          = PARMCH_RFC2833EVT_TX_PLT;
    parmInfo.pvParmValue    = &PLType;
    LogEvent(INFO_MSG, "Setting Parameter PARMCH_RFC2833EVT_TX_PLT to 101");
    if (ipm_SetParm(mediaDevHandle, &parmInfo, EV_SYNC) < 0) {
   
		LogEvent(INFO_ERROR, "ipm_SetParm() error");
    }
    // set the RX Payload Type
    PLType              = 101;
    parmInfo.eParm      = PARMCH_RFC2833EVT_RX_PLT;
    parmInfo.pvParmValue = &PLType;
    LogEvent(INFO_MSG,"Setting Parameter PARMCH_RFC2833EVT_RX_PLT to 101");
    if (ipm_SetParm(mediaDevHandle, &parmInfo, EV_SYNC) < 0) {
        LogEvent(INFO_ERROR, "ipm_SetParm() error");
    }
}

void StartMedia (bool addVideo)
{
    // Describes media, both local and remote, and starts the RTP streaming

    // First, set up the IP Media Info struct using the Media info parsed from the incoming SDP
	// Codecs would also be set according to parsed info, but we're simplifying to just use G.711
    IPM_MEDIA_INFO MediaInfo;
    memset(&MediaInfo, 0, sizeof(IPM_MEDIA_INFO));
    int miCnt = 0;

    MediaInfo.MediaData[miCnt].eMediaType = MEDIATYPE_AUDIO_REMOTE_RTP_INFO;
    MediaInfo.MediaData[miCnt].mediaInfo.PortInfo.unPortId = RemoteAudioPort;
    strcpy(MediaInfo.MediaData[miCnt].mediaInfo.PortInfo.cIPAddress, RemoteIPAddr);
    miCnt++;

    MediaInfo.MediaData[miCnt].eMediaType = MEDIATYPE_AUDIO_REMOTE_RTCP_INFO;
    MediaInfo.MediaData[miCnt].mediaInfo.PortInfo.unPortId = RemoteAudioPort+1;
    strcpy(MediaInfo.MediaData[miCnt].mediaInfo.PortInfo.cIPAddress, RemoteIPAddr);
    miCnt++;

    if (addVideo) {
        // remote video RTP port 
        MediaInfo.MediaData[miCnt].eMediaType = MEDIATYPE_VIDEO_REMOTE_RTP_INFO;
        MediaInfo.MediaData[miCnt].mediaInfo.PortInfo.unPortId = RemoteVideoPort;
        strcpy(MediaInfo.MediaData[miCnt].mediaInfo.PortInfo.cIPAddress, RemoteIPAddr);
        miCnt++;

        // remote video RTCP port
        MediaInfo.MediaData[miCnt].eMediaType = MEDIATYPE_VIDEO_REMOTE_RTCP_INFO;
        MediaInfo.MediaData[miCnt].mediaInfo.PortInfo.unPortId = RemoteVideoPort + 1; 
        strcpy(MediaInfo.MediaData[miCnt].mediaInfo.PortInfo.cIPAddress, RemoteIPAddr);
        miCnt++;
    }

    MediaInfo.MediaData[miCnt].eMediaType = MEDIATYPE_REMOTE_CODER_INFO;
    MediaInfo.MediaData[miCnt].mediaInfo.CoderInfo.eCoderType = CODER_TYPE_G711ULAW64K;
    MediaInfo.MediaData[miCnt].mediaInfo.CoderInfo.eFrameSize = (eIPM_CODER_FRAMESIZE) 20;
    MediaInfo.MediaData[miCnt].mediaInfo.CoderInfo.unFramesPerPkt = 1;
    MediaInfo.MediaData[miCnt].mediaInfo.CoderInfo.eVadEnable = CODER_VAD_DISABLE;
    MediaInfo.MediaData[miCnt].mediaInfo.CoderInfo.unCoderPayloadType = 0;
    MediaInfo.MediaData[miCnt].mediaInfo.CoderInfo.unRedPayloadType = 0;
    miCnt++;

    MediaInfo.MediaData[miCnt].eMediaType = MEDIATYPE_LOCAL_CODER_INFO;
    MediaInfo.MediaData[miCnt].mediaInfo.CoderInfo.eCoderType = CODER_TYPE_G711ULAW64K;
    MediaInfo.MediaData[miCnt].mediaInfo.CoderInfo.eFrameSize = (eIPM_CODER_FRAMESIZE) 20;
    MediaInfo.MediaData[miCnt].mediaInfo.CoderInfo.unFramesPerPkt = 1;
    MediaInfo.MediaData[miCnt].mediaInfo.CoderInfo.eVadEnable =CODER_VAD_DISABLE;
    MediaInfo.MediaData[miCnt].mediaInfo.CoderInfo.unCoderPayloadType = 0;
    MediaInfo.MediaData[miCnt].mediaInfo.CoderInfo.unRedPayloadType = 0;    
    miCnt++;

    if (addVideo) {
        // remote video codec
        MediaInfo.MediaData[miCnt].eMediaType = MEDIATYPE_VIDEO_REMOTE_CODER_INFO;
        MediaInfo.MediaData[miCnt].mediaInfo.VideoCoderInfo.eCoderType = CODER_TYPE_H263;
        MediaInfo.MediaData[miCnt].mediaInfo.VideoCoderInfo.unVersion = 0;
        MediaInfo.MediaData[miCnt].mediaInfo.VideoCoderInfo.unCoderPayloadType = 100;
        miCnt++;

        // local video codec
        MediaInfo.MediaData[miCnt].eMediaType = MEDIATYPE_VIDEO_LOCAL_CODER_INFO;
        MediaInfo.MediaData[miCnt].mediaInfo.VideoCoderInfo.eCoderType = CODER_TYPE_H263;
        MediaInfo.MediaData[miCnt].mediaInfo.VideoCoderInfo.unVersion = 0;
        MediaInfo.MediaData[miCnt].mediaInfo.VideoCoderInfo.unCoderPayloadType = 100;
        miCnt++;
    }
    MediaInfo.unCount = miCnt;

    // Enable RFC2833 DTMF detection
    SetDtmfModeRFC2833();

    LogEvent(INFO_MSG, 
             "Starting media. Local audio port - %d  Local video port - %d  Remote audio port - %d  Remote video port - %d",
             LocalAudioPort, LocalVideoPort, RemoteAudioPort, RemoteVideoPort);

	// Strictly speaking, this ought to be done async, but this simplifies the demo
    if (ipm_StartMedia(mediaDevHandle, &MediaInfo, DATA_IP_TDM_BIDIRECTIONAL, EV_SYNC) != 0) {
        LogEvent(INFO_ERROR, "ipm_StartMedia() on device %d Failed - %s", mediaDevHandle, ATDV_ERRMSGP(mediaDevHandle));
    } else {
        mediaStreaming = true;
    }
}

// ******************************** End IPML ************************************

// ******************************** Audio ************************************

void GetDigits()
{
    // check if we already have a pending dx_getdig() call
    // Flag not really needed as GetDigits is only called from a single place
    // beyond iniitalization.
    if (isGetDigitPending == true) {
        LogEvent(INFO_MSG, "dx_Getdig is pending.  Getdigits() not called");
        return;
    }

    DV_TPT tpt[1];
    dx_clrtpt(tpt, 1);
    tpt[0].tp_type  = IO_EOT;
    tpt[0].tp_termno    = DX_MAXDTMF;
    tpt[0].tp_length    = 1; // look for 1 digit
    tpt[0].tp_flags = TF_MAXDTMF;

    DTMFdigit.dg_value[0] = 0;

    dx_clrdigbuf(dtmfDevHandle);

    if (dx_getdig(dtmfDevHandle, tpt, &DTMFdigit, EV_ASYNC) < 0) {
        LogEvent(INFO_ERROR, "dx_getdig() failed. Err = %s", ATDV_ERRMSGP(dtmfDevHandle));
    } else {
        LogEvent(INFO_MSG, "Getdigits() called");
        isGetDigitPending = true;
    }

}



void StopDTMF()
{
    if (dx_stopch(dtmfDevHandle, EV_SYNC) == -1)
        LogEvent(INFO_ERROR, "Cannot stop DTMF device");
    else
        LogEvent(INFO_MSG, "DTMF device stopped");
}
// ******************************** End Audio ************************************


// ******************************** MultiMedia ************************************

void PlayVideoFile(int whatToPlay, bool videoCall)
{

	// Do a Multimedia play on an audio/video file set, or on just the audio portion
	// of the file set

    MM_MEDIA_ITEM_LIST audioMediaList;
    MM_MEDIA_ITEM_LIST videoMediaList;

    MM_PLAY_RECORD_LIST playRecordList[2];
    MM_PLAY_INFO        playInfo;

	if (filePlayInProgress) {
		LogEvent(INFO_MSG, "Multimedia file play in progress.  Not retrying play.");
		return;
	}

    if (videoCall) {
		LogEvent(INFO_MSG, "Play audio/video files");
        audioMediaList.item.audio.codec.unBitsPerSample     = 16;
        audioMediaList.item.audio.codec.unCoding            = 1;
        audioMediaList.item.audio.codec.unSampleRate        = 8000;

        audioMediaList.item.audio.unMode                    = 0x0020;
        audioMediaList.item.audio.unOffset                  = 0;
        audioMediaList.ItemChain                            = EMM_ITEM_EOT;

        playRecordList[0].ItemType                          = EMM_MEDIA_TYPE_AUDIO;
        playRecordList[0].list                              = &audioMediaList; 

        videoMediaList.item.video.unMode                    = 0;
        videoMediaList.item.video.codec.FramesPerSec        = EMM_VIDEO_FRAMESPERSEC_UNDEFINED;
        videoMediaList.item.video.codec.ImageHeight     = EMM_VIDEO_IMAGE_HEIGHT_UNDEFINED;
        videoMediaList.item.video.codec.ImageWidth      = EMM_VIDEO_IMAGE_WIDTH_UNDEFINED;
        videoMediaList.item.video.codec.Level           = EMM_VIDEO_LEVEL_UNDEFINED;
        videoMediaList.item.video.codec.Profile         = EMM_VIDEO_PROFILE_UNDEFINED;
        videoMediaList.ItemChain                        = EMM_ITEM_EOT;

        playRecordList[1].ItemType                      = EMM_MEDIA_TYPE_VIDEO;
        playRecordList[1].list                          = &videoMediaList;

        playRecordList[0].ItemChain                     = EMM_ITEM_CONT;
        playRecordList[1].ItemChain                     = EMM_ITEM_EOT;
    } else {
        // Audio-only multimedia call
		LogEvent(INFO_MSG, "Play audio-only file");
        audioMediaList.item.audio.codec.unBitsPerSample     = 16;
        audioMediaList.item.audio.codec.unCoding            = 1;
        audioMediaList.item.audio.codec.unSampleRate        = 8000;

        audioMediaList.item.audio.unMode                    = 0x0020;
        audioMediaList.item.audio.unOffset                  = 0;
        audioMediaList.ItemChain                            = EMM_ITEM_EOT;

        playRecordList[0].ItemType                          = EMM_MEDIA_TYPE_AUDIO;
        playRecordList[0].list                              = &audioMediaList;
        playRecordList[0].ItemChain                         = EMM_ITEM_EOT;
    }


    playInfo.unVersion      = 0x100;
    playInfo.eFileFormat        = EMM_FILE_FORMAT_PROPRIETARY;
    playInfo.list               = playRecordList;

	switch (whatToPlay)
	{
		case PLAY_MENU:
			audioMediaList.item.audio.szFileName = "menu.pcm";
			videoMediaList.item.video.szFileName = "menu.vid";
		break;
		case PLAY_CLIP1:
			audioMediaList.item.audio.szFileName = "clip1.pcm";
			videoMediaList.item.video.szFileName = "clip1.vid";
			hangupWhenDone = true;
		break;
		case PLAY_CLIP2:
			audioMediaList.item.audio.szFileName = "clip2.pcm";
			videoMediaList.item.video.szFileName = "clip2.vid";
			hangupWhenDone = true;
		break;
	}


    // start play
    LogEvent(INFO_MSG, "Playing -> audioF = %s, videoF = %s",  audioMediaList.item.audio.szFileName, videoMediaList.item.video.szFileName);
    if (mm_Play(videoDevHandle, &playInfo, NULL, (void *) 0) < 0) {
        LogEvent(INFO_ERROR, "mm_Play() failed");
    }
	else {
		filePlayInProgress = true;
	}
}

void StopMMPlay()
{
    LogEvent(INFO_MSG, "Stopping MultiMedia Play");
    MM_STOP mmStopInfo[2];
    MM_STOP_DETAILS mmStopDetails;
    memset(&mmStopDetails, 0, sizeof(MM_STOP_DETAILS));

    mmStopInfo[0].unVersion = 0;
    mmStopInfo[0].ItemChain = EMM_ITEM_CONT;
    mmStopInfo[0].ItemType = EMM_STOP_VIDEO_PLAY;
    mmStopInfo[0].details = mmStopDetails;
    mmStopInfo[0].next = &mmStopInfo[1];

    mmStopInfo[1].unVersion = 0;
    mmStopInfo[1].ItemChain = EMM_ITEM_EOT;
    mmStopInfo[1].ItemType = EMM_STOP_AUDIO_PLAY;
    mmStopInfo[1].details = mmStopDetails;
    mmStopInfo[1].next = NULL;

    if (mm_Stop(videoDevHandle, mmStopInfo, NULL) < 0) {
        LogEvent(INFO_ERROR, "mm_Stop() for Play failed");
    }
}

// ******************************** End MultiMedia ************************************

void ShutdownSystem()
{
    // Close up open and potentially in-use devices for a clean
    // shutdown

    LogEvent(INFO_MSG, "Shutting system down...");

    UnMonitorIPMfromDTMF();

    // Stop any multimedia activity
    StopMMPlay();
	// Do a dev_Disconnect on IPM and MM
	DisconnectIPMfromMM();
    if (mm_Close(videoDevHandle, NULL) == -1)
        LogEvent(INFO_ERROR, "Cannot close multimedia device");
    else
        LogEvent(INFO_MSG, "Multimedia device closed");

    // Stop any DTMF activity 
    StopDTMF();
	// Disconnect media and voice timeslots
    UnMonitorIPMfromDTMF();
    if (dx_close(dtmfDevHandle) == -1)
        LogEvent(INFO_ERROR, "Cannot close DTMF device");
    else
        LogEvent(INFO_MSG, "DTMF device closed");

    // Stop IPML (RTP) streaming
    StopMedia();

	// Close IPML device
    if (ipm_Close(mediaDevHandle, NULL) == -1)
        LogEvent(INFO_ERROR, "Cannot close media device");
    else
        LogEvent(INFO_MSG, "Media device closed");

	// Reset the GC channel
	if (gc_ResetLineDev(SIPDevHandle, EV_ASYNC) == -1) {
        LogEvent(ERROR_GC,  "Cannot reset SIP device");
    } else {
        LogEvent(INFO_MSG, "SIP device reset");
    }

    // Close GC channel
    if (gc_Close(SIPDevHandle) == -1) {
        LogEvent(ERROR_GC,  "Cannot close SIP device");
    } else {
        LogEvent(INFO_MSG, "SIP device closed");
    }

    // Finally, stop GlobalCall
    gc_Stop();
}


//////////////// Main Program starts here ///////////////////////

int main()
{
    // Set up exit handlers
    signal(SIGINT,  intr_handler); 
    signal(SIGTERM, intr_handler);
    signal(SIGILL, intr_handler);
    signal(SIGFPE, intr_handler);
    signal(SIGBREAK, intr_handler);
    signal(SIGABRT, intr_handler);

    // Local Variables
    GC_START_STRUCT     gcstart;
    IPCCLIB_START_DATA  ipData;
    IP_VIRTBOARD        ipVirtBoard;

	OpenLogFile();

    // Start the GC Subsystem
    LogEvent(INFO_MSG,  "Starting Global Call ....");

    // Start with default values in GC virtual board
    INIT_IP_VIRTBOARD(&ipVirtBoard);

    // Don't exceed max number of licenses!
    ipVirtBoard.sip_max_calls = SIP_MAX_CALLS;
    ipVirtBoard.total_max_calls = TOTAL_MAX_CALLS;
    ipVirtBoard.h323_max_calls = IP_CFG_NO_CALLS;

    ipData.num_boards = 1;
    ipVirtBoard.sip_msginfo_mask = IP_SIP_MSGINFO_ENABLE | IP_SIP_MIME_ENABLE;

    ipData.board_list = &ipVirtBoard;
    INIT_IPCCLIB_START_DATA(&ipData, 1,&ipVirtBoard);

    // Enable 3PCC mode
    ipData.max_parm_data_size       = IP_CFG_PARM_DATA_MAXLEN;
    ipData.media_operational_mode   = MEDIA_OPERATIONAL_MODE_3PCC;

    memset(&gcstart, 0, sizeof(GC_START_STRUCT));

    CCLIB_START_STRUCT ccstart[] = 
    {
        {"GC_IPM_LIB",NULL},
        {"GC_H3R_LIB",&ipData},                 
        {"GC_DM3CC_LIB",NULL},
    };
    gcstart.cclib_list = ccstart;
    gcstart.num_cclibs = 3;

    // load specified libraries.
    if ( gc_Start( &gcstart ) != GC_SUCCESS ) {
        LogEvent(ERROR_GC, "Cannot start GlobalCall");
        return 0;
    } else {
        LogEvent(INFO_MSG, "GlobalCall Started");
    }


    // Open a voice device for dtmf detection
    char dtmfDevName[32] = "dxxxB1C1";
    dtmfDevHandle = dx_open(dtmfDevName, 0);
    LogEvent(INFO_MSG, "%s opened, handle = %d", dtmfDevName, dtmfDevHandle);
    if (dtmfDevHandle == -1) {
        LogEvent(INFO_ERROR, "dx_open() failed on device %s", dtmfDevName);
    }

    // Open a single GC SIP device.  Note there is no media (ipm) device
    // as we are doing 3PCC, and will be handling the RTP streaming ourselves
    char SIPDevName[32] = ":N_iptB1T1:P_SIP";

    if (gc_OpenEx(&SIPDevHandle, SIPDevName, EV_SYNC, NULL) < 0) {
        LogEvent(ERROR_GC, "Cannot open %s ", SIPDevName);
        gc_Stop();
        return 0;
    } else {
        LogEvent(INFO_MSG, "%s opened, handle = %d", SIPDevName, SIPDevHandle);
    }

    // Open a single IPM device
    char mediaDevName[32] = "ipmB1C1";
    LogEvent(INFO_MSG, "Opening media device %s", mediaDevName);
    if ( (mediaDevHandle = ipm_Open(mediaDevName, NULL, EV_ASYNC)) == -1) {
        LogEvent(INFO_MSG, "ipm_Open(devicename=%s, mode=EV_ASYNC) Failed", mediaDevName);
    }


    // Now move into an event handling loop

    // Events are sent to:
    //		- GC event processing routine
    //	    - "other" event processing routine
    // which will react accordingly
    // Keyboard interrupt will end loop and exit program
    appRunning = true;
    while (appRunning) {
        if (sr_waitevt(10) != -1) {

            METAEVENT metaevent;
            if (gc_GetMetaEvent(&metaevent) == -1) {
                LogEvent(INFO_ERROR, "error from gc_GetMetaEvent\n");
            }
            if (metaevent.flags == GCME_GC_EVENT) {
                processGCEvent(metaevent);
            } else {
                long  evtDev  = sr_getevtdev();
                long  evtType = sr_getevttype();
                void *pEvtData = sr_getevtdatap();
                long  evtLen  = sr_getevtlen();
                processOtherEvent(evtDev, evtType, pEvtData, evtLen);
            }
        }
    }

    ShutdownSystem();
	CloseLogFile();
    return 0;

} // end main

// ********************************* Event Processing *****************************

// GC event processing 
// Implcit state machine here accepts a single incoming call. Real simple.
// Plays a file, 

void processGCEvent(METAEVENT metaevent)
{
    GC_PARM_BLKP  gcParmBlk = NULL;
    int state = -1;
    int ret_val = 0;

    // Save CRN
    curCallCRN= metaevent.crn;

    switch (metaevent.evttype) {
    case GCEV_UNBLOCKED:
        LogEvent(INFO_MSG, "Channel unblocked event received (GCEV_UNBLOCKED)");
        // Wait for incoming call on channel
        if (gc_WaitCall(metaevent.evtdev, NULL, NULL, 0, EV_ASYNC) != GC_SUCCESS) {
            LogEvent(ERROR_GC, "Error waiting for incoming call");
        } else {
            LogEvent(INFO_MSG, "gc_WaitCall issued");
        }
        break;
    case GCEV_OFFERED:      // incoming call
        {
            LogEvent(INFO_MSG, "Incoming call event received (GCEV_OFFERED)");
            // Want to use the "Ex" versions to handle possible data > 255 bytes
            GC_PARM_BLKP        parm_blkp = (GC_PARM_BLKP)metaevent.extevtdatap;
            GC_PARM_DATA_EXT l_parm;
            INIT_GC_PARM_DATA_EXT( &l_parm);

            // SDP for the OK to be sent back to the caller
            char sdpAnswerString[1024];

            while (( gc_util_next_parm_ex( parm_blkp, &l_parm)) == GC_SUCCESS) {
                //LogEvent(INFO_MSG, "set id = %d	parm id = %d", l_parm.set_ID, l_parm.parm_ID);
                switch (l_parm.set_ID) {               
                case IPSET_SDP:
                    switch (l_parm.parm_ID) {
                    case IPPARM_SDP_OFFER:
                        {
                            LogEvent(INFO_MSG, "IPPARM_SDP_OFFER:");
                            char* l_parmData = new char[l_parm.data_size+1];
                            strncpy( l_parmData,(char*)l_parm.pData, l_parm.data_size);
                            l_parmData[l_parm.data_size]='\0';
                            LogEvent(INFO_MSG, "%s", l_parmData);

                            // Import/parse incoming SDP
                            ParseOfferSDP(l_parmData, &useVideo);
                        }                           
                        break;
                        //}
                    case IPPARM_SDP_ANSWER:
                        LogEvent(INFO_MSG, "IPPARM_SDP_ANSWER");
                        break;
                    }
                    break;
                }    // end l_parm.set_ID switch     
            } // end gc_util_next_parm_ex loop

            // Using some paramters collected from the SDP just parsed, setup and start
            // the IPML (RTP) streaming
            StartMedia(useVideo);

            // Answer (SIP OK) using the SDP created.
            LogEvent(INFO_MSG, "Inserting SDP Answer");
            GC_PARM_BLKP parmblkp = NULL; // input parameter block pointer 
            GC_PARM_BLKP retblkp = NULL; // return parameter block
            int retval = GC_SUCCESS;

            // Use the sdpapi to build an SDP, using the media types, ports
            // and IP address extracted from the SDP that came with the INVITE
            sdpSessionDescription answerSDP;
            answerSDP.clear();
            BuildAnswerSDP(&answerSDP, useVideo);
            if (answerSDP.exportSDP(sdpAnswerString, 1024, false) == 0) {
                LogEvent(INFO_MSG, "Export of Answer SDP successful");
            } else {
                LogEvent(INFO_MSG, "Export of Answer SDP failed");
            }
            // Add 1 to strlen for null termination 
            int len = strlen(sdpAnswerString) + 1;
            LogEvent(INFO_MSG, "length = %d\n\nSDP Answer = \n%s", len, sdpAnswerString);

            // Note that the "ex" version is used here to handle potentially long parameter (SDP)
            if (gc_util_insert_parm_ref_ex(&parmblkp,
                                           IPSET_SDP,
                                           IPPARM_SDP_ANSWER,
                                           len,
                                           sdpAnswerString) == -1) {
                LogEvent(INFO_ERROR, "gc_util_insert_parm_ref_ex failed");
            }

            if (gc_SetUserInfo(GCTGT_GCLIB_CRN, curCallCRN, parmblkp, GC_SINGLECALL) == -1) {
                LogEvent(INFO_ERROR, "gc_SetUserInfo failed");
            }

            gc_util_delete_parm_blk(parmblkp);

            LogEvent(INFO_MSG, "Answering Call");
            if (gc_AnswerCall(curCallCRN, 0, EV_ASYNC) != GC_SUCCESS) {
                LogEvent(ERROR_GC, "gc_AnswerCall(crn=0x%lx, # of rings=0, mode=EV_ASYNC) Failed", curCallCRN);
            }

            // Look for incoming DTMFs
            GetDigits();
        }
        break;
    case GCEV_ANSWERED:    // Application has answered incoming call
        LogEvent(INFO_MSG, "Answered call event received (GCEV_ANSWERED)");
		hangupWhenDone = false;

		// DTMFs are being detected in the RTP stream
        MonitorIPMforDTMF();

        // Play an A/V file using mm_Play.  Dev_connects have already been done
        PlayVideoFile(PLAY_MENU, useVideo);

        break;
    case GCEV_DISCONNECTED:
        LogEvent(INFO_MSG, "Call disconnect event received (GCEV_DISCONNECTED)");
		HangUp();
        break;
    case GCEV_DROPCALL:
        LogEvent(INFO_MSG, "Drop call event received (GCEV_DROPCALL)");
        if (gc_ReleaseCallEx(metaevent.crn, EV_ASYNC) == -1) {
            LogEvent(ERROR_GC, "Error releasing call");
        }
        break;
    case GCEV_RELEASECALL:
        LogEvent(INFO_MSG, "Release call event recieved (GCEV_RELEASECALL)");
        break;
    case GCEV_CALLINFO:
        LogEvent(INFO_MSG, "Call info event received (GCEV_CALLINFO)");
        break;
    case GCEV_TASKFAIL:
        LogEvent(INFO_ERROR, "Task fail event received (GCEV_TASKFAIL)");
        break;
    default:
        LogEvent(INFO_ERROR, "Unprocessed GC event 0x%x (%ld) on device handle %d",
                 metaevent.evttype, metaevent.evttype, metaevent.evtdev);
        break;
    }
}

void processOtherEvent(long evtDev, long evtType, void *pEvtData, long evtLen)
{
    switch (evtType) {

	// Application events
	// App is simple - a single menu to play a choice of 2 audio/video clips
	case APP_MENU_1:
            LogEvent(INFO_MSG, "Event APP_MENU_1 received");
			PlayVideoFile(PLAY_CLIP1, useVideo);
		break;

	case APP_MENU_2:
            LogEvent(INFO_MSG, "Event APP_MENU_2 received");
			PlayVideoFile(PLAY_CLIP2, useVideo);
		break;



    // IPML events
    case IPMEV_OPEN:
        {
            LogEvent(INFO_MSG, "Event IPMEV_OPEN received on device handle %d",
                     evtDev);
            // A good place to do the MM device is when IPML is finished opening
            // When MM is open, they can be dev-connected together
            // Note that opening is async only - sync not possible.
            char videoDevName[32] = "mmB1C1";
            if ((videoDevHandle = mm_Open(videoDevName, NULL, NULL)) < 0) {
                LogEvent(INFO_ERROR, "mm_Open(devicename=%s) Failed", videoDevName);
            } else {
                LogEvent(INFO_MSG, "mm_Open(devicename=%s) successfully called.  Handle = %d", videoDevName, videoDevHandle);
			}
        }
        break;

	case IPMEV_LISTEN:
        LogEvent(INFO_MSG, "Event IPMEV_LISTEN received on device handle %d",
                 evtDev);
        // Get IP address and audio/video port info for this channel
        GetLocalIPMInfo ();
		break;

    case IPMEV_GET_LOCAL_MEDIA_INFO:
        LogEvent(INFO_MSG, "Event IPMEV_GET_LOCAL_MEDIA_INFO received on device handle %d",
                 evtDev);
        SaveLocalIPMInfo (pEvtData);
        break;


        // Dev Connect events
    case DMEV_CONNECT:
        {
            // Two events are returned on a dev_Connect - one for each of the
            // devices connected.  Make sure both are returned before moving on
            static bool gotIPM, gotMM;
            LogEvent(INFO_MSG, "Event DMEV_CONNECT received on device handle %d",
                     evtDev);
            if (evtDev == mediaDevHandle)
                gotIPM = true;
            if (evtDev == videoDevHandle)
                gotMM = true;
            if (gotIPM && gotMM)
                // Get IP address and audio/video port info for this channel
                GetLocalIPMInfo ();
        }
        break;
    case DMEV_CONNECT_FAIL:
        LogEvent(INFO_MSG, "Event DMEV_CONNECT_FAIL received on device handle %d",
                 evtDev);
        break;
    case DMEV_DISCONNECT:
        LogEvent(INFO_MSG, "Event DMEV_DISCONNECT received on device handle %d",
                 evtDev);
        break;
    case DMEV_DISCONNECT_FAIL:
        LogEvent(INFO_MSG, "Event DMEV_DISCONNECT_FAIL received on device handle %d",
                 evtDev);
        break;

        // Voice events
    case TDX_PLAY:
		{
			LogEvent(INFO_MSG, "Event TDX_PLAY received on device handle %d",
					 evtDev);
			filePlayInProgress = false;
			long termReason;
			if ((termReason = ATDX_TERMMSK(voiceDevHandle)) == -1)
					LogEvent(INFO_ERROR, "Unable to get termination reason on TDX_PLAY");
			else  {
				LogEvent(INFO_MSG, "TDX_Play termination reason = %d", termReason);
			}

			// Done with file - close it now
			dx_fileclose(fhandle);

 			// Hang up when a clip (as opposed to a menu) is played
			if (hangupWhenDone) {
				HangUp();
			}
		}
       break;
    case TDX_GETDIG:
        {
            LogEvent(INFO_MSG, "Event TDX_GETDIG received on device handle %d",
                     evtDev);
            isGetDigitPending = false; 
            long termno = 0;
            termno = ATDX_TERMMSK(dtmfDevHandle);
            LogEvent(INFO_MSG, "GetDigit termination -> 0x%x", termno);
            if (termno & TM_MAXDTMF) {
                // Got a digit - normal operation
				char digitCollected = DTMFdigit.dg_value[0];
                LogEvent(INFO_MSG, "Got DTMF digit - %c", digitCollected);

				// If digit is one of the menu choices, ( 1 or 2 )
				// submit an event to the SRL as an application-defined event
				switch (digitCollected - 0x30)  {
				case 1:
				case 2:
					if (sr_putevt(0, digitCollected - 0x30, 0, 0, 0) == -1)
						LogEvent(INFO_ERROR, "Error adding app event to SRL");
						break;
				}
                // Reset to get another digit
                GetDigits();
            }
        }
        break;

    case TDX_ERROR:
        LogEvent(INFO_ERROR, "Event TDX_ERROR (%d) %s received on device handle %d",
                 ATDV_LASTERR(voiceDevHandle), ATDV_ERRMSGP(voiceDevHandle), evtDev);
        break;

        // MultiMedia events
    case MMEV_OPEN:
        LogEvent(INFO_MSG, "Event MMEV_OPEN received on device handle %d",
                 evtDev);
        // Connect IPM and MM devices
        ConnectIPMtoMM();
        break;
    case MMEV_PLAY_ACK:                   
        LogEvent(INFO_MSG, "Event MMEV_PLAY_ACK received on device handle %d",
                 evtDev);
        break;
    case MMEV_STOP_ACK:                   
        LogEvent(INFO_MSG, "Event MMEV_STOP_ACK received on device handle %d",
                 evtDev);
        break;
    case MMEV_PLAY: 
        LogEvent(INFO_MSG, "Event MMEV_PLAY received on device handle %d",
                 evtDev);
		filePlayInProgress = false;
		// Hang up when a clip (as opposed to a menu) is played
		if (hangupWhenDone) {
			HangUp();
        }
        break;
	case MMEV_PLAY_ACK_FAIL:
        LogEvent(INFO_ERROR, "Event MMEV_PLAY_FAIL_ACK received on device handle %d",
                 evtDev);
        break;
	case MMEV_STOP_ACK_FAIL:
        LogEvent(INFO_MSG, "Event MMEV_STOP_FAIL_ACK received on device handle %d",
                 evtDev);
        break;

    default:
        LogEvent(INFO_ERROR, "Unprocessed other event 0x%x (%ld) on device handle %d",
                 evtType, evtType, evtDev);
    }
}