/* ***** BEGIN LICENSE BLOCK ***** 
 * Version: RCSL 1.0/RPSL 1.0 
 *  
 * Portions Copyright (c) 1995-2002 RealNetworks, Inc. All Rights Reserved. 
 *      
 * The contents of this file, and the files included with this file, are 
 * subject to the current version of the RealNetworks Public Source License 
 * Version 1.0 (the "RPSL") available at 
 * http://www.helixcommunity.org/content/rpsl unless you have licensed 
 * the file under the RealNetworks Community Source License Version 1.0 
 * (the "RCSL") available at http://www.helixcommunity.org/content/rcsl, 
 * in which case the RCSL will apply. You may also obtain the license terms 
 * directly from RealNetworks.  You may not use this file except in 
 * compliance with the RPSL or, if you have a valid RCSL with RealNetworks 
 * applicable to this file, the RCSL.  Please see the applicable RPSL or 
 * RCSL for the rights, obligations and limitations governing use of the 
 * contents of the file.  
 *  
 * This file is part of the Helix DNA Technology. RealNetworks is the 
 * developer of the Original Code and owns the copyrights in the portions 
 * it created. 
 *  
 * This file, and the files included with this file, is distributed and made 
 * available on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 
 * EXPRESS OR IMPLIED, AND REALNETWORKS HEREBY DISCLAIMS ALL SUCH WARRANTIES, 
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS 
 * FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 
 * 
 * Technology Compatibility Kit Test Suite(s) Location: 
 *    http://www.helixcommunity.org/content/tck 
 * 
 * Contributor(s): 
 *  
 * ***** END LICENSE BLOCK ***** */

#include "ihxtprofile.h"
#ifdef _MAC_UNIX
#include <Carbon/Carbon.h>
#endif
#include "hxcom.h"

// standard includes
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <math.h>
#include "ihxpckts.h"

using namespace std;

#ifdef _WINDOWS
#include <direct.h>
#elif defined _LINUX
#include <unistd.h>
#include <dlfcn.h>
#endif

#include "ihxtencodingjob.h"
#include "ihxtlogsystem.h"

#include "mediasinkencoder.h"

const UINT32 MAX_BUFFER_SIZE = 255;

const UINT32 kulVideoWidth = 320;
const UINT32 kulVideoHeight = 240;


// Sample event sink helper class -- prints out encoding status
// Registered for events in CMediaSinkEncoderApp::CreateJob()
class CSampleEventSink :
public IHXTEventSink
{
public:
    CSampleEventSink() 
    {
	m_ulRefCount = 0;
	m_bEncodingStarted = FALSE;
	m_bEncodingFinished = FALSE;
	m_bSecondPassStarted = FALSE;
    }
    
    //IUnknown methods
    STDMETHOD_( ULONG32, AddRef )()
    {
	return InterlockedIncrement(&m_ulRefCount);
    }
    STDMETHOD_( ULONG32, Release )()
    {
	if (InterlockedDecrement(&m_ulRefCount) > 0)
	{
	    return m_ulRefCount;
	}
	
	delete this;
	return 0;
    }
    STDMETHOD( QueryInterface )( REFIID riid, void** ppvObj )
    {
	if(!ppvObj)
	{
	    return HXR_POINTER;				
	}
	
	if(IsEqualIID(IID_IHXTEventSink, riid))			
	{							
	    AddRef();					
	    *ppvObj = (IHXTEventSink*)this;
	    return HXR_OK;					
	}
	
	return HXR_FAIL;
    }
    
    // IHXTEventSink methods
    STDMETHOD( HandleEvent )( EHXTEvent eEvent, UINT32 *puValue, const char *cszValue, IUnknown *pUnknown )
    {
	switch(eEvent)
	{
	case (eEventEncodingStarted):
	    {
		m_bEncodingStarted = TRUE;
		printf("\nEncoding started..\n");
		break;
	    }
	    
	case (eEventTwoPassEncodingUsingAnalysisStarted):
	    {
		m_bSecondPassStarted = TRUE;
		printf("\nSecond encode pass started..\n");
		break;
	    }
	    
	case(eEventEncodingFinished):
	    {
		m_bEncodingFinished = TRUE;
		printf("\nEncoding finished..\n");
		break;
	    }			
	}
	
	return HXR_OK;
    }
    
private:
    UINT32 m_ulRefCount;
    
public:
    BOOL m_bEncodingStarted;
    BOOL m_bSecondPassStarted;
    BOOL m_bEncodingFinished;
};

CMediaSinkEncoderApp::CMediaSinkEncoderApp(istream& cInput) :
m_pFactory(NULL)
,m_pJob(NULL)
,m_pFileLogObserver(NULL)
,m_pLogSystem(NULL)
,m_RmsessionDLL(0)	
,m_pEventSink(NULL)
,m_bEncodeAudio(FALSE)
,m_bEncodeVideo(FALSE)
,m_bEncodeEvents(FALSE)
,m_bEncodeTwoPass(FALSE)
,m_cInput(cInput)
,m_pEventPin(NULL)
,m_pAudioPin(NULL)
,m_pVideoPin(NULL)
{
}


CMediaSinkEncoderApp::~CMediaSinkEncoderApp()
{
}

HX_RESULT CMediaSinkEncoderApp::SetupDLLPaths()
{
    HX_RESULT res = HXR_OK;
    
    cout << "Step 1: Setting up DLL Paths" << endl;
    
    // This sample app must be run from the %sdk_install_dir%\bin directory.  Applications using the
    // SDK can be run from anywhere as long as SetDLLAccessPaths is called with the location of
    // the SDK binaries.
    char szCurrentDir[MAX_BUFFER_SIZE] = "";
#ifdef _WINDOWS	
    _getcwd(szCurrentDir, MAX_BUFFER_SIZE);
#elif defined _LINUX
    getcwd(szCurrentDir, MAX_BUFFER_SIZE);
#elif defined _MAC_UNIX    
    getcwd(szCurrentDir, MAX_BUFFER_SIZE);       
#endif
    
    if (strstr(szCurrentDir, "bin") == NULL)
    {
	cout << "********************************************************************" << endl;
	cout << "*** Sample app must be run from %sdk_install_dir%\\bin directory ***" << endl;
	cout << "********************************************************************" << endl << endl;
    }
    
    // Note that if you do not call SetDLLAccessPaths, the SDK will assume the current working
    // directory matches the structure of %sdk_install_dir%\bin.  Namely, that the current working directory
    // has plugins, codecs, tools, and common subdirectories with the appropriate DLLs.  As long as this application is run
    // from the %sdk_install_dir%\bin directory, you can skip this step (just comment out the call to SetDLLAccessPath
    // to try it)
    
    // Create a null delimited, double-null terminated string containing DLL category
    // name/path pairs.  The default structure of the SDK has discrete subdirectories for each DLL category.  If
    // you wish, all DLLs can be placed within a single directory.  The path passed to SetDLLAccessPath simply
    // needs to reflect this.
    
    memset(m_szDllPath, 0, 2048);
    UINT32 ulNumChars = 0;
    
    // Assume the current working directory is %sdk_install_dir%\bin -- this would normally be set to an absolute path
#ifdef _WINDOWS	
    const char* pCurrentDir = ".\\";
#elif defined _LINUX
    const char* pCurrentDir = "./";
#elif defined _MAC_UNIX
    const char* pCurrentDir = "./";
#endif
    
    ulNumChars += sprintf(m_szDllPath+ulNumChars, "%s=%s%s", "DT_Plugins", pCurrentDir, "plugins") + 1;
    ulNumChars += sprintf(m_szDllPath+ulNumChars, "%s=%s%s", "DT_Codecs", pCurrentDir, "codecs") + 1;
    ulNumChars += sprintf(m_szDllPath+ulNumChars, "%s=%s%s", "DT_EncSDK", pCurrentDir, "tools") + 1;
    ulNumChars += sprintf(m_szDllPath+ulNumChars, "%s=%s%s", "DT_Common", pCurrentDir, "common") + 1;
    
    // Load the rmsession binary (DLL/so) to get the SetDLLAccessPath entry point
    if (SUCCEEDED(res))
    {		
#ifdef _WINDOWS
	m_RmsessionDLL = ::LoadLibrary(".\\tools\\encsession.dll");
#elif defined _LINUX
	m_RmsessionDLL = dlopen("./tools/encsession.so", RTLD_LAZY);
#elif defined _MAC_UNIX
        CFURLRef bundleURL = NULL;
        bundleURL = CFURLCreateWithFileSystemPath ( kCFAllocatorDefault, CFSTR("./tools/encsession.bundle"), kCFURLPOSIXPathStyle, true );
       
        if (bundleURL)
        {
            m_RmsessionDLL = CFBundleCreate ( kCFAllocatorDefault, bundleURL );
            CFRelease(bundleURL);
        }       
#endif
	if (m_RmsessionDLL)
	{
	    // Get the SetDLLAccessPath entry point
	    FPRMBUILDSETDLLACCESSPATH fpSetDllAccessPath = NULL;
#ifdef _WINDOWS
	    fpSetDllAccessPath = (FPRMBUILDSETDLLACCESSPATH)(::GetProcAddress(m_RmsessionDLL, "SetDLLAccessPath"));		
#elif defined _LINUX
	    fpSetDllAccessPath = (FPRMBUILDSETDLLACCESSPATH)(dlsym(m_RmsessionDLL, "SetDLLAccessPath"));
#elif defined _MAC_UNIX
	    fpSetDllAccessPath = (FPRMBUILDSETDLLACCESSPATH) CFBundleGetFunctionPointerForName(m_RmsessionDLL, CFSTR("SetDLLAccessPath"));
#endif
	    // Set the DLL access paths
	    if (fpSetDllAccessPath)
	    {
		res = (*fpSetDllAccessPath)(m_szDllPath);
	    }
	    else
	    {
		res = HXR_FAIL;
	    }
	}
	else
	{
	    res = HXR_FAIL;
	}
    }
    
    // Create job factory
    // Get the RMACreateRMJobFactory entry point from the rmsesion binary
    if (SUCCEEDED(res))
    {		
	FPCREATEJOBFACTORY fpCreateClassFactory = NULL;	
#ifdef _WINDOWS
	fpCreateClassFactory = (FPCREATEJOBFACTORY)(::GetProcAddress(m_RmsessionDLL, "HXTCreateJobFactory"));
#elif defined _LINUX
	fpCreateClassFactory = (FPCREATEJOBFACTORY)(dlsym(m_RmsessionDLL, "HXTCreateJobFactory"));
#elif defined _MAC_UNIX
	fpCreateClassFactory = (FPCREATEJOBFACTORY) CFBundleGetFunctionPointerForName(m_RmsessionDLL, CFSTR("HXTCreateJobFactory"));
#endif
	// Create the job class factory
	if (fpCreateClassFactory)
	{
	    res = (*fpCreateClassFactory)(&m_pFactory);
	}
	else
	{
	    res = HXR_FAIL;
	}
    }
    
    return res;
}

HX_RESULT CMediaSinkEncoderApp::InitializeLogSystem()
{
    HX_RESULT res = HXR_OK;
    
    cout << "Step 2: Set up log system" << endl;
    
    // Check if logging should be enabled
    char szQuestion1[MAX_BUFFER_SIZE] = "";
    cout << "  Log errors and warnings to file (y/n)?: ";		
    m_cInput >>  szQuestion1;
    
    if (szQuestion1[0] == 'y' || szQuestion1[0] == 'Y' )
    {
	cout << "  Enter log filename: ";		
	char szFile1[MAX_BUFFER_SIZE] = "";
	m_cInput >>  szFile1;
	
	// Create/init log system -- note that it is necessary to do this prior to instantiating the log observer
	if (SUCCEEDED(res))
	{
	    res = m_pFactory->CreateInstance(IID_IHXTLogSystem, (IUnknown**)&m_pLogSystem);
	    
	    if (SUCCEEDED(res))
	    {
#ifdef _WINDOWS
		res = m_pLogSystem->SetTranslationFileDirectory(".\\tools");
#elif defined _LINUX
		res = m_pLogSystem->SetTranslationFileDirectory("./tools");
#elif defined _MAC_UNIX
		res = m_pLogSystem->SetTranslationFileDirectory("./tools");
#endif
	    }
	}
	
	// Create/init file observer
	if (SUCCEEDED(res))
	    res = m_pFactory->CreateInstance(IID_IHXTFileObserver, (IUnknown**)&m_pFileLogObserver);
	
	if( SUCCEEDED(res) )
	    res = m_pFileLogObserver->SetFormat(Detailed);
	
	if (SUCCEEDED(res))
	    res = m_pFileLogObserver->Init(szFile1);
    }
    
    
    return res;
}

HX_RESULT CMediaSinkEncoderApp::CreateJob()
{
    HX_RESULT res = HXR_OK;
    
    // Use the class factory to create the encoding job object
    if (SUCCEEDED(res))
	res = m_pFactory->CreateInstance(IID_IHXTEncodingJob, (IUnknown**)&m_pJob);
    
    // Register event sink
    if (SUCCEEDED(res))
    {
	// Create event sink helper class
	m_pEventSink = new CSampleEventSink;
	m_pEventSink->AddRef();
	
	// Get the event manager
	IHXTEventManager* pEventMgr = NULL;
	res = m_pJob->GetEventManager(&pEventMgr);
	
	// Subscribe for events
	if (SUCCEEDED(res))
	    res = pEventMgr->Subscribe(m_pEventSink);
	
	HX_RELEASE(pEventMgr);
    }
    
    return res;
}

HX_RESULT CMediaSinkEncoderApp::SetupInput()
{
    HX_RESULT res = HXR_OK;
    
    cout << "Step 4: Setting up custom input media sink" << endl;
    
    // Create the property bag used to initialize the input
    IHXTPropertyBag* pInitParams = NULL;
    if (SUCCEEDED(res))
        res = m_pFactory->CreateInstance(IID_IHXTPropertyBag, (IUnknown**)&pInitParams);
    
    // Set the plugin type
    if (SUCCEEDED(res))
	res = pInitParams->SetString(kPropPluginType, kValuePluginTypeInputMediaSink);
    
    // Media samples generated in StartEncoding are not strictly real-time
    if (SUCCEEDED(res))
	res = pInitParams->SetBool(kPropIsRealTime, FALSE);
    
    // Create the input
    IHXTInput* pInput = NULL;
    if (SUCCEEDED(res))
	res = m_pFactory->BuildInstance(IID_IHXTInput, pInitParams, (IUnknown**)&pInput);
    
    // Set the input on the encoding job
    if (SUCCEEDED(res))
	res = m_pJob->SetInput(pInput);
    
    // Print name of input plugin
    if (SUCCEEDED(res))
    {
	const char* szPluginName = "";
	res = pInput->GetString(kPropPluginName, &szPluginName);
	
	if (SUCCEEDED(res))
	    cout << "  Using input plugin: " << szPluginName << endl;
    }
    
    // Configure audio pin
    if (SUCCEEDED(res))
    {
	// Check if audio should be encoded
	char szInput[MAX_BUFFER_SIZE] = "";
	cout << "  Would you like to encode audio (y/n)?: ";		
	m_cInput >>  szInput;
	
	// Enable audio pin
	if (szInput[0] == 'y' || szInput[0] == 'Y')
	{
	    m_bEncodeAudio = TRUE;
	    
	    // Get the audio pin
	    IUnknown* pUnk = NULL;
	    res = pInput->GetUnknown( kPropAudioInputPin, &pUnk );
	    
	    if (SUCCEEDED(res))
		res = pUnk->QueryInterface(IID_IHXTMediaInputPin, (void**)&m_pAudioPin);
	    
	    // Enable pin
	    if (SUCCEEDED(res))
		res = m_pAudioPin->SetPinEnabled(TRUE);
	    
	    // Set audio format -- see ihxtaudioformat.h for enum definitions
	    IHXTAudioPinFormat* pPinFormat = NULL;
	    if (SUCCEEDED(res))
		res = pUnk->QueryInterface(IID_IHXTAudioPinFormat, (void**)&pPinFormat);
	    
	    if (SUCCEEDED(res))
		res = pPinFormat->SetSampleRate(44100);
	    
	    if (SUCCEEDED(res))
		res = pPinFormat->SetSampleFormat(HXT_AUDIO_FORMAT_NE_16_2);
	    
	    if (SUCCEEDED(res))
		res = pPinFormat->SetChannelFormat(HXT_AUDIO_MONO);
	    
	    HX_RELEASE(pPinFormat);
	    HX_RELEASE(pUnk);
	}
    }
    
    
    // Configure video pin
    if (SUCCEEDED(res))
    {
	// Check if video should be encoded
	char szInput[MAX_BUFFER_SIZE] = "";
	cout << "  Would you like to encode video (y/n)?: ";		
	m_cInput >>  szInput;
	
	// Enable video pin
	if (szInput[0] == 'y' || szInput[0] == 'Y')
	{
	    m_bEncodeVideo = TRUE;
	    
	    // Get the video pin			
	    IUnknown* pUnk = NULL;
	    res = pInput->GetUnknown( kPropVideoInputPin, &pUnk );
	    
	    if (SUCCEEDED(res))
		res = pUnk->QueryInterface(IID_IHXTMediaInputPin, (void**)&m_pVideoPin);
	    
	    // Enable pin
	    if (SUCCEEDED(res))
		res = m_pVideoPin->SetPinEnabled(TRUE);
	    
	    // Set video format
	    IHXTVideoPinFormat* pPinFormat = NULL;
	    if (SUCCEEDED(res))
		res = pUnk->QueryInterface(IID_IHXTVideoPinFormat, (void**)&pPinFormat);
	    
	    if (SUCCEEDED(res))
		res = pPinFormat->SetFrameDimensions(kulVideoWidth, kulVideoHeight);
	    
	    if (SUCCEEDED(res))
		res = pPinFormat->SetColorFormat(HXT_VIDEO_FORMAT_BGRA32_INVERTED);
	    
	    if (SUCCEEDED(res))
		res = pPinFormat->SetFrameRate(30.0);
	    
	    HX_RELEASE(pPinFormat);
	    HX_RELEASE(pUnk);
	}
    }
    
    // Configure event pin
    if (SUCCEEDED(res))
    {
	// Check if events should be encoded
	char szInput[MAX_BUFFER_SIZE] = "";
	cout << "  Would you like to encode events (y/n)?: ";		
	m_cInput >>  szInput;
	
	// Enable events pin
	if (szInput[0] == 'y' || szInput[0] == 'Y')
	{
	    m_bEncodeEvents = TRUE;
	    
	    // Get the event pin
	    IUnknown* pUnk = NULL;
	    res = pInput->GetUnknown(kPropEventInputPin, &pUnk);
	    
	    if (SUCCEEDED(res))
		res = pUnk->QueryInterface(IID_IHXTMediaInputPin, (void**)&m_pEventPin);
	    
	    // Enable pin
	    if (SUCCEEDED(res))
		res = m_pEventPin->SetPinEnabled(TRUE);
	    
	    HX_RELEASE(pUnk);
	}
    }
    
    HX_RELEASE(pInput);
    HX_RELEASE(pInitParams);
    
    return res;
}


HX_RESULT CMediaSinkEncoderApp::SetupPrefilters()
{
    HX_RESULT res = HXR_OK;
    
    cout << "Step 5: Setting up prefilters" << endl;
    
    IHXTInput* pInput = NULL;
    res = m_pJob->GetInput(&pInput);
    
    // Check if the input source has a video track
    BOOL bInputHasVideo = FALSE;
    if (SUCCEEDED(res))
	res = pInput->GetBool(kPropHasVideo, &bInputHasVideo);
    
    // Only add video prefilters if the input source has video
    if (SUCCEEDED(res) && bInputHasVideo)
    {
	char szInput[MAX_BUFFER_SIZE] = "";
	
	// Check if the black-level filter should added
	cout << "  Would you like to use the black-level prefilter (y/n)?: ";		
	m_cInput >>  szInput;
	
	// Add the black-level prefilter
	if (szInput[0] == 'y' || szInput[0] == 'Y')
	{
	    // Create the property bag used to initialize the prefilter
	    IHXTPropertyBag* pInitParams = NULL;
	    if (SUCCEEDED(res))
		res = m_pFactory->CreateInstance(IID_IHXTPropertyBag, (IUnknown**)&pInitParams);
	    
	    // Set plugin type
	    if (SUCCEEDED(res))
		res = pInitParams->SetString(kPropPluginType, kValuePluginTypePrefilterBlackLevel);
	    
	    // Set plugin name.  Note that plugin name is typically specified when creating prefilters -- this
	    // is different than the code to create inputs (plugin name wasn't specified then).  This is because
	    // there were several input readers that the Producer SDK selected from, while there is only a single
	    // prefilter that matches the black level prefilter plugin type.
	    if (SUCCEEDED(res))
		res = pInitParams->SetString(kPropPluginName, kValuePluginNamePrefilterBlackLevel);
	    
	    // Create the prefilter
	    IHXTPrefilter* pPrefilter = NULL;
	    if (SUCCEEDED(res))
		res = m_pFactory->BuildInstance(IID_IHXTPrefilter, pInitParams, (IUnknown**)&pPrefilter);
	    
	    // Add the prefilter to the input
	    if (SUCCEEDED(res))
		res = pInput->AddPrefilter(pPrefilter);
	    
	    HX_RELEASE(pInitParams);
	    HX_RELEASE(pPrefilter);
	}
    }
    
    // Check if the input source has an audio track
    BOOL bInputHasAudio = FALSE;
    if (SUCCEEDED(res))
	res = pInput->GetBool(kPropHasAudio, &bInputHasAudio);
    
    // Only add audio prefilters if the input source has audio
    if (SUCCEEDED(res) && bInputHasAudio)
    {
	char szInput[MAX_BUFFER_SIZE] = "";
	
	// Check if the audio limiter filter should added
	cout << "  Would you like to use the audio limiter prefilter (y/n)?: ";		
	m_cInput >>  szInput;
	
	// Add the audio limiter
	if (szInput[0] == 'y' || szInput[0] == 'Y')
	{
	    // Create the property bag used to initialize the prefilter
	    IHXTPropertyBag* pInitParams = NULL;
	    if (SUCCEEDED(res))
		res = m_pFactory->CreateInstance(IID_IHXTPropertyBag, (IUnknown**)&pInitParams);
	    
	    // Set plugin type
	    if (SUCCEEDED(res))
		res = pInitParams->SetString(kPropPluginType, kValuePluginTypePrefilterAudioGain);
	    
	    // Set plugin name.  Note that plugin name is typically specified when creating prefilters -- this
	    // is different than the code to create inputs (plugin name wasn't specified then).  This is because
	    // there were several input readers that the Producer SDK selected from, while there is only a single
	    // prefilter that matches the black level prefilter plugin type.
	    if (SUCCEEDED(res))
		res = pInitParams->SetString(kPropPluginName, kValuePluginNamePrefilterAudioGain);
	    
	    // Create the prefilter
	    IHXTPrefilter* pPrefilter = NULL;
	    if (SUCCEEDED(res))
		res = m_pFactory->BuildInstance(IID_IHXTPrefilter, pInitParams, (IUnknown**)&pPrefilter);
	    
	    // Set the gain level.  Note that this property could have also been set as a part of
	    // the pInitParams property bag.
	    if (SUCCEEDED(res))
		res = pPrefilter->SetDouble(kPropAudioLimiterGain, 6.0);
	    
	    // Add the prefilter to the input
	    if (SUCCEEDED(res))
		res = pInput->AddPrefilter(pPrefilter);
	    
	    HX_RELEASE(pInitParams);
	    HX_RELEASE(pPrefilter);
	}
    }
    
    HX_RELEASE(pInput);
    
    return res;
}


HX_RESULT CMediaSinkEncoderApp::SetupDestination()
{
    HX_RESULT res = HXR_OK;
    
    cout << "Step 6: Setting up destination" << endl;
    
    // Create the output profile
    IHXTOutputProfile* pOutputProfile = NULL;
    if (SUCCEEDED(res))
	res = m_pFactory->BuildInstance(IID_IHXTOutputProfile, NULL, (IUnknown**)&pOutputProfile);
    
    if (SUCCEEDED(res))
	res = m_pJob->AddOutputProfile(pOutputProfile);
    
    // Get the destination pathname
    cout << "  Enter output file pathname: ";
    char szOutputPathname[MAX_BUFFER_SIZE] = "";
    m_cInput >>  szOutputPathname;
    
    // Create the property bag used to initialize the destination
    IHXTPropertyBag* pInitParams = NULL;
    if (SUCCEEDED(res))
        res = m_pFactory->CreateInstance(IID_IHXTPropertyBag, (IUnknown**)&pInitParams);
    
    // Set the plugin type
    if (SUCCEEDED(res))
	res = pInitParams->SetString(kPropPluginType, kValuePluginTypeDestinationFile);
    
    // Set the plugin name
    if (SUCCEEDED(res))
	res = pInitParams->SetString(kPropPluginName, kValuePluginNameFileDestRealMedia);
    
    // Set the pathname
    if (SUCCEEDED(res))
	res = pInitParams->SetString(kPropOutputPathname, szOutputPathname);
    
    // Create the destination
    IHXTDestination* pDest = NULL;
    if (SUCCEEDED(res))
	res = m_pFactory->BuildInstance(IID_IHXTDestination, pInitParams, (IUnknown**)&pDest);
    
    // Set the destination on the output profile
    if (SUCCEEDED(res))
	res = pOutputProfile->AddDestination(pDest);
    
    HX_RELEASE(pOutputProfile);
    HX_RELEASE(pDest);
    HX_RELEASE(pInitParams);
    
    return res;
}


HX_RESULT CMediaSinkEncoderApp::SetupMediaProfile()
{
    HX_RESULT res = HXR_OK;
    
    cout << "Step 7: Setting up media profile" << endl;
    
    // Get the output profile
    IHXTOutputProfile* pOutputProfile = NULL;
    res = m_pJob->GetOutputProfile(0, &pOutputProfile);
    
    // Create the media profile
    IHXTMediaProfile* pMediaProfile = NULL;
    if (SUCCEEDED(res))
        res = m_pFactory->BuildInstance(IID_IHXTMediaProfile, NULL, (IUnknown**)&pMediaProfile);
    
    // Set the media profile
    if (SUCCEEDED(res))
	res = pOutputProfile->SetMediaProfile(pMediaProfile);
    
    // Get the current video mode
    if (SUCCEEDED(res))
    {
	const char* szVideoMode = "";
	res = pMediaProfile->GetString(kPropVideoMode, &szVideoMode);
	
	if (SUCCEEDED(res))
	    cout << "  Current video mode: " << szVideoMode << endl;
    }
    
    // Set the video encoding mode
    UINT32 ulVideo = 0;
    cout << "  Please enter the video mode you would like for encoding.\n    " \
	"0) Normal\n    1) Smoothest Motion\n    2) Sharpest Image\n    3) Slideshow\n? ";
    m_cInput >>  ulVideo;
    
    if (SUCCEEDED(res))
    {
	if (ulVideo == 0)
	    res = pMediaProfile->SetString(kPropVideoMode, kValueVideoModeNormal);
	else if (ulVideo == 1)
	    res = pMediaProfile->SetString(kPropVideoMode, kValueVideoModeSmooth);
	else if (ulVideo == 2)
	    res = pMediaProfile->SetString(kPropVideoMode, kValueVideoModeSharp);
	else if (ulVideo == 3)
	    res = pMediaProfile->SetString(kPropVideoMode, kValueVideoModeSharp);
	else
	    res = HXR_FAIL;
    }
    
    // Get the current audio mode
    if (SUCCEEDED(res))
    {
	const char* szAudioMode = "";
	res = pMediaProfile->GetString(kPropAudioMode, &szAudioMode);
	
	if (SUCCEEDED(res))
	    cout << "  Current audio mode: " << szAudioMode << endl;
    }
    
    // Set the audio encoding mode
    UINT32 ulAudio = 0;
    cout << "  Please enter the audio mode you would like for encoding.\n" \
	"    0) Music\n    1) Voice\n? ";
    m_cInput >>  ulAudio;
    
    if (SUCCEEDED(res))
    {
	if (ulAudio == 0)
	    res = pMediaProfile->SetString(kPropAudioMode, kValueAudioModeMusic);
	else if (ulAudio == 1)
	    res = pMediaProfile->SetString(kPropAudioMode, kValueAudioModeVoice);
	else
	    res = HXR_FAIL;
    }
    
    
    HX_RELEASE(pOutputProfile);
    HX_RELEASE(pMediaProfile);
    
    return res;
}



HX_RESULT CMediaSinkEncoderApp::SetupAudiences()
{
    HX_RESULT res = HXR_OK;
    
    cout << "Step 8: Setting up audiences" << endl;
    
    // Get the output profile
    IHXTOutputProfile* pOutputProfile = NULL;
    res = m_pJob->GetOutputProfile(0, &pOutputProfile);
    
    // Get the media profile
    IHXTMediaProfile* pMediaProfile = NULL;
    if (SUCCEEDED(res))
	res = pOutputProfile->GetMediaProfile(&pMediaProfile);
    
    // Create the audience enumerator
    IHXTAudienceEnumerator* pAudienceEnum = NULL;
    if (SUCCEEDED(res))
	res = m_pFactory->CreateInstance(IID_IHXTAudienceEnumerator, (IUnknown**)&pAudienceEnum);
    
    if (SUCCEEDED(res))
    {
	res = pAudienceEnum->SetProfileDirectory("audiences");
    }
    
    // Get the number of audiences in the profile directory
    UINT32 ulNumAudiences = 0;
    if (SUCCEEDED(res))
	ulNumAudiences = pAudienceEnum->GetAudienceCount();
    
    // Print all of the available audiences
    cout << "  Available audiences:" << endl;
    for (UINT32 i=0; i<ulNumAudiences && SUCCEEDED(res); i++)
    {
	IHXTAudience* pAudience = NULL;
	res = pAudienceEnum->GetAudience(i, &pAudience, NULL);
	
	if (SUCCEEDED(res))
	{
            const char* szName = "";
            res = pAudience->GetString(kPropObjectName, &szName);
	    
	    cout << "    " << i << ") " << szName << endl;
	}
	
	HX_RELEASE(pAudience);
    }
    
    // Select audiences to add to the media profile
    if (SUCCEEDED(res))
    {
	cout << "  Select an audience to add to your encode, -1 to stop adding audiences." << endl;
	cout << "  Select multiple audiences to create a SureStream encode." << endl;
	
	INT32 nAudience = -1;
	do
	{			
	    m_cInput >>  nAudience;
	    if (nAudience != -1)
	    {
		// Get the audience from the enumerator
		IHXTAudience* pAudience = NULL;
		res = pAudienceEnum->GetAudience((UINT32)nAudience, &pAudience, NULL);
		
		// Add the audience to the media profile
		if (SUCCEEDED(res))
		    res = pMediaProfile->AddAudience(pAudience);
		
		HX_RELEASE(pAudience);
	    }
	    
	} while (nAudience != -1 && SUCCEEDED(res));
    }	
    
    // Set some a/v codec properties
    if (SUCCEEDED(res))
    {		
	// Get max startup latency
	UINT32 ulMaxStartupLatency = 0;
	cout << "  Enter max video startup latency in seconds (typical value is 4): ";
	m_cInput >>  ulMaxStartupLatency;
	
	// Get max time between keyframes
	UINT32 ulMaxTimeBetweenKeyframes = 0;
	cout << "  Enter max time between video keyframes in seconds (typical value is 10): ";
	m_cInput >>  ulMaxTimeBetweenKeyframes;

	// Get encoding complexity
	UINT32 ulEncodeComplexity = 0;
	cout << "  Please enter the encoding complexity (speed vs. quality tradeoff):\n    " \
	    "0) Low\n    1) Medium\n    2) High\n    ? ";
	m_cInput >>  ulEncodeComplexity;
	
	// Set the values on the appropriate streams within each audience
	UINT32 ulNumAudiences = pMediaProfile->GetAudienceCount();
	for (UINT32 i=0; i<ulNumAudiences && SUCCEEDED(res); i++)
	{
	    // Get the audience
	    IHXTAudience* pAudience = 0;
	    res = pMediaProfile->GetAudience(i, &pAudience);
	    
	    if (SUCCEEDED(res))
	    {
		// Iterate through all streams within the audience
		UINT32 ulNumStreams = pAudience->GetStreamConfigCount();
		for (UINT32 j=0; j<ulNumStreams && SUCCEEDED(res); j++)
		{
		    // Get the stream
		    IHXTStreamConfig* pStream = NULL;
		    res = pAudience->GetStreamConfig(j, &pStream);
		    
		    // Check if the current stream is a video stream
		    if (SUCCEEDED(res))
		    {
			const char* szStreamType = "";
			res = pStream->GetString(kPropPluginType, &szStreamType);
			
			// Set max startup latency and max time between keyframes on all video streams
			if (SUCCEEDED(res) && strcmp(szStreamType, kValuePluginTypeVideoStream) == 0)
			{
			    res = pStream->SetDouble(kPropMaxStartupLatency, ulMaxStartupLatency);
			    
			    if (SUCCEEDED(res))
				res = pStream->SetDouble(kPropMaxTimeBetweenKeyFrames, ulMaxTimeBetweenKeyframes);
			}
		    }

		    // Set encoding complexity on audio/video streams
		    if (SUCCEEDED(res))
		    {
			switch (ulEncodeComplexity)
			{
			case 0:
			    res = pStream->SetString(kPropEncodingComplexity, kValueEncodingComplexityLow);
			    break;
			case 1:
			    res = pStream->SetString(kPropEncodingComplexity, kValueEncodingComplexityMedium);
			    break;
			default:
			case 2:
			    res = pStream->SetString(kPropEncodingComplexity, kValueEncodingComplexityHigh);
			    break;
			}
		    }
		    
		    HX_RELEASE(pStream);
		}
	    }
	    
	    HX_RELEASE(pAudience);
	}
    }
    
    // Add an event stream to each audience
    if (SUCCEEDED(res) && m_bEncodeEvents)
    {
	// Set the values on video streams within each audience
	UINT32 ulNumAudiences = pMediaProfile->GetAudienceCount();
	for (UINT32 i=0; i<ulNumAudiences && SUCCEEDED(res); i++)
	{
	    // Get the audience
	    IHXTAudience* pAudience = 0;
	    res = pMediaProfile->GetAudience(i, &pAudience);
	    
	    if (SUCCEEDED(res))
	    {
		// Create the property bag used to initialize the stream
		IHXTPropertyBag* pInitParams = NULL;
		if (SUCCEEDED(res))
		    res = m_pFactory->CreateInstance(IID_IHXTPropertyBag, (IUnknown**)&pInitParams);
		
		// Set the plugin type
		if (SUCCEEDED(res))
		    res = pInitParams->SetString(kPropPluginType, kValuePluginTypeEventStream);
		
		// Set the plugin name
		if (SUCCEEDED(res))
		    res = pInitParams->SetString(kPropPluginName, kValuePluginNameTransformRealEvents);
		
		// Create the stream
		IHXTStreamConfig* pStream = NULL;
		if (SUCCEEDED(res))
		    res = m_pFactory->BuildInstance(IID_IHXTStreamConfig, pInitParams, (IUnknown**)&pStream);
		
		HX_RELEASE(pInitParams);
		
		// Add the stream to the current audience (assumes the audience doesn't 
		// already have an event stream
		if (SUCCEEDED(res))
		    res = pAudience->AddStreamConfig(pStream);
		
		HX_RELEASE(pStream);
	    }
	    
	    HX_RELEASE(pAudience);
	}
    }
    
    HX_RELEASE(pOutputProfile);
    HX_RELEASE(pMediaProfile);
    HX_RELEASE(pAudienceEnum);
    
    return res;
}


HX_RESULT CMediaSinkEncoderApp::SetupJob()
{
    HX_RESULT res = HXR_OK;
    
    cout << "Step 9: Setting up encoding job" << endl;
    
    char szInput[MAX_BUFFER_SIZE] = "";
    
    // Check if the encode should be two-pass
    cout << "  Perform two-pass encode (y/n)?: ";		
    m_cInput >>  szInput;
    
    // Set two-pass encode state
    if (szInput[0] == 'y' || szInput[0] == 'Y')
    {
	res = m_pJob->SetBool(kPropEnableTwoPass, TRUE);
	
	// SDK application needs to know if encode is single-pass or two-pass.  In the
	// two-pass case, the SDK application must pass all media samples twice to the
	// media sink (see CMediaSinkEncoderApp::StartEncoding)
	m_bEncodeTwoPass = TRUE;
    }
    
    // Get metadata property bag
    IHXTPropertyBag* pMetadata = NULL;
    if (SUCCEEDED(res))
	res = m_pJob->GetMetadata(&pMetadata);
    
    // Get the title
    if (SUCCEEDED(res))
    {
	cout << "  Enter clip title: ";
	m_cInput >>  szInput;
    }
    
    // Set the title	
    if (SUCCEEDED(res))
	res = pMetadata->SetString(kPropTitle, szInput);
    
    // Get the author
    if (SUCCEEDED(res))
    {
	cout << "  Enter clip author: ";
	m_cInput >>  szInput;
    }
    
    // Set the author	
    if (SUCCEEDED(res))
	res = pMetadata->SetString(kPropAuthor, szInput);
    
    
    HX_RELEASE(pMetadata);
    
    return res;
}

HX_RESULT CMediaSinkEncoderApp::SerializeJob()
{
    HX_RESULT res = HXR_OK;
    
    cout << "Step 10: Serialize job -- optional step" << endl;
    
    char szInput[MAX_BUFFER_SIZE] = "";
    
    // Check if the job should be saved
    cout << "  Save job to file (y/n)?: ";		
    m_cInput >>  szInput;
    
    // Serialize the job
    if (szInput[0] == 'y' || szInput[0] == 'Y')
    {
	cout << "  Enter job filename: ";		
	m_cInput >>  szInput;
	
	// Get the serialization interface
	IHXTUserConfigFile* pSerializer = NULL;
	res = m_pJob->QueryInterface(IID_IHXTUserConfigFile, (void**)&pSerializer);
	
	// Save the job to a file
	if (SUCCEEDED(res))
	    res = pSerializer->WriteToFile(szInput);
	
	HX_RELEASE(pSerializer);
    }
    
    return res;
}


HX_RESULT CMediaSinkEncoderApp::StartEncoding()
{
    HX_RESULT res = HXR_OK;
    
    cout << "Step 11: Starting encode" << endl;
    
    // Start the encode -- this call will block until encoding has started
    if (SUCCEEDED(res))
	res = m_pJob->StartEncoding(FALSE);
    
    // Wait until the sdk has signalled that encoding has started before passing
    // media samples
    while (SUCCEEDED(res) && m_pEventSink->m_bEncodingStarted == FALSE)
    {
#ifdef _WIN32
	Sleep(1000);
#elif defined _UNIX
	sleep(1);
#endif
    }
    
    // Encode samples
    if (SUCCEEDED(res))
    {
	res = EncodeSamples();
	
	// All media samples must be passed to the encoding engine twice during
	// a two-pass encode
	if (SUCCEEDED(res) && m_bEncodeTwoPass)
	{
	    // Wait until the sdk has signalled that encoding has finished
	    while (m_pEventSink->m_bSecondPassStarted == FALSE)
	    {
#ifdef _WIN32
		Sleep(1000);
#elif defined _UNIX
		sleep(1);
#endif
	    }
	    
	    res = EncodeSamples();
	}
    }
    
    // Wait until the sdk has signalled that encoding has finished
    while (SUCCEEDED(res) && m_pEventSink->m_bEncodingFinished == FALSE)
    {
#ifdef _WIN32
	Sleep(1000);
#elif defined _UNIX
	sleep(1);
#endif
    }
    
    // Unsubscribe the event sink -- not needed anymore.  Preserve the StartEncoding() return code.
    if (m_pEventSink)
    {
	// Get the event manager
	IHXTEventManager* pEventMgr = NULL;
	HX_RESULT resEvent = m_pJob->GetEventManager(&pEventMgr);
	
	// Unsubscribe event sink
	if (SUCCEEDED(resEvent))
	    resEvent = pEventMgr->Unsubscribe(m_pEventSink);
	
	HX_RELEASE(pEventMgr);
	HX_RELEASE(m_pEventSink);
    }
    
    return res;
}


HX_RESULT CMediaSinkEncoderApp::EncodeSamples()
{
    HX_RESULT res = HXR_OK;
    
    // Create media sample allocator
    IHXTSampleAllocator* pAllocator = NULL;
    res = m_pFactory->CreateInstance(IID_IHXTSampleAllocator, (IUnknown**)&pAllocator);
    
    // simulation:  one pass each second
    UINT32 z=0;
    for (z=0; z < 30 && SUCCEEDED(res); z ++)
    {
	// Encode an event sample
	if (m_bEncodeEvents && SUCCEEDED(res))
	{
	    // Send URL event sample
	    if ( z == 0)
	    { 
		// Create event sample -- note that event sample comes from class factory
		IHXTEventSample* pEventSample = NULL;
		res = m_pFactory->CreateInstance(IID_IHXTEventSample, (IUnknown**)&pEventSample);
		
		// Set target URL
		if (SUCCEEDED(res))
		    res = pEventSample->SetAction(HXEventMediaSample_URL, "http://www.real.com", NULL);
		
		// Set start/finish time
		if (SUCCEEDED(res))
		    res = pEventSample->SetTime(0, 6000);
		
		// Encode sample
		if (SUCCEEDED(res))
		    res = m_pEventPin->EncodeSample( pEventSample );
		
		HX_RELEASE(pEventSample);
		printf( "Sent URL Event\n" );
	    }
	    
	    //Send a custom event sample
	    else if ( z == 5 )
	    { 
		IHXTEventSample* pEventSample = NULL;
		IHXBuffer* pBuffer = NULL;
		IHXValues* pValues = NULL;
		if (SUCCEEDED(res))
		    res = m_pFactory->CreateInstance(IID_IHXTEventSample, (IUnknown**)&pEventSample);
		if (SUCCEEDED(res))
		    res = m_pFactory->CreateInstance(CLSID_IHXValues, (IUnknown**)&pValues);
		if (SUCCEEDED(res))
		    res = m_pFactory->CreateInstance(CLSID_IHXBuffer, (IUnknown**)&pBuffer);
		
		if (SUCCEEDED(res))
		{ // load up a IHXValues with name/value pairs to encode
		    pValues->SetPropertyULONG32("myulong32prop", 200);
		    pBuffer->Set((BYTE *)"my dog spot", 12);
		    pValues->SetPropertyCString("mystringprop", pBuffer);
		}
		
		if (SUCCEEDED(res))
		    res=pEventSample->SetAction(HXEventMediaSample_Custom, "mycustomevent", pValues);
		if (SUCCEEDED(res))
		    res=pEventSample->SetTime( 5000, 15000);
		if (SUCCEEDED(res))
		    res = m_pEventPin->EncodeSample( pEventSample );
		
		HX_RELEASE(pBuffer);
		HX_RELEASE(pValues);
		HX_RELEASE(pEventSample);
		printf( "Sent Custom Event\n" );
	    }
	}
	
	// Encode on second's worth of audio.
	// Note that in practice it is a better idea to alternate between passing audio and video 
	// samples so that one doesn't get too far ahead of the other in terms of sample 
	// start times.  Doing otherwise (say passing 5 seconds worth of audio at a time followed by 5 seconds worth
	// of video) may result in higher than expected memory consumption.
	if (m_bEncodeAudio && SUCCEEDED(res))
	{  
	    // Get media sample -- sample: 1 channel, 16 bits/sample, 44.1 khz
	    // Note that the the size of the buffer being allocated should exactly match the size
	    // of the audio chunk (do not allocate a larger buffer).  Since audio start/end timestamps only 
	    // have millisecond resolution, some portions of the Producer SDK determine the audio chunk size
	    // based on the buffer size, not the start/end timestamps.
	    IHXTMediaSample* pMediaSample=NULL;
	    res = pAllocator->GetMediaSampleOfSize(44100*sizeof(UINT16), &pMediaSample);
	    
	    if (SUCCEEDED(res))
	    {
		// Get the sample data buffer -- note use of GetDataStartForWriting instead of 
		// GetDataStartForReading since the buffer is being written to
		UINT16 *y=(UINT16*)pMediaSample->GetDataStartForWriting();
		
		float fifth = 3.0f/2.0f ;
		float minthird = 6.0f/5.0f ;
		float f0 = 440.0f ; // Hz
		float pi = 3.1415926535f;
		
		// Construct a minor chord
		for (int i = 0 ; i < 44100 ; i++)
		{ 
		    float x = (float) (2*pi*i/44100.0 * f0);
		    y[i] = (UINT16)(32000 * (1.0/3.0) * (sin(x) + sin(minthird*x)+ sin(fifth*x)));
		}
	    }
	    
	    // Set time
	    if (SUCCEEDED(res))
		res = pMediaSample->SetTime(z*1000, (z+1)*1000);
	    
	    // Encode sample
	    // Note: Do NOT reuse (read from or write to the data buffer) the media sample after passing it 
	    // to the input pin.  The media sample is not automatically memcpy'ed, and some other object may 
	    // have a refcount on it and modify the buffer on another thread.  Just release the media
	    // sample and get another one from the allocator.
	    if (SUCCEEDED(res))
		res = m_pAudioPin->EncodeSample( pMediaSample );
	    
	    printf("Sent audio sample time: %d\n", z*1000);
	    HX_RELEASE(pMediaSample);
	}
	
	// Encode one seconds worth of video frames
	// Note that in practice it is a better idea to alternate between passing audio and video 
	// samples so that one doesn't get too far ahead of the other in terms of sample 
	// start times.  Doing otherwise (say passing 5 seconds worth of audio at a time followed by 5 seconds worth
	// of video) may result in higher than expected memory consumption.
	if (m_bEncodeVideo && SUCCEEDED(res))
	{ 
	    for (UINT32 ulFrameCount=0; ulFrameCount < 30; ulFrameCount++)
	    {
		// Get media sample -- kulVideoWidth x kulVideoHeight x HXT_VIDEO_FORMAT_BGRA32_INVERTED
		IHXTMediaSample* pMediaSample=NULL;
		res = pAllocator->GetMediaSampleOfSize( kulVideoWidth * kulVideoHeight * sizeof(UINT32), &pMediaSample);
		
		// Create green/blue colored scrolling frame
		if (SUCCEEDED(res))
		{
		    // Get the sample data buffer -- note use of GetDataStartForWriting instead of 
		    // GetDataStartForReading since the buffer is being written to
		    UINT32* pSampleBuffer = (UINT32*)pMediaSample->GetDataStartForWriting();
		    for ( int i = 0;  i < kulVideoHeight; i++ )
		    {
			static UINT32 ulColorBand = 0;
			ulColorBand++;				
			
			for( int j =0; j < kulVideoWidth; j++ )
			{
			    *(pSampleBuffer+(i*kulVideoWidth)+j) = 0x0000FF00 + ulColorBand;
			}
		    }
		}
		
		// Set start/end time
		if (SUCCEEDED(res))
		    res = pMediaSample->SetTime(z*1000 + ulFrameCount * 33, z*1000 + ulFrameCount * 33 + 10);
		
		// Encode sample
		// Note: Do NOT reuse (read from or write to the data buffer) the media sample after passing it 
		// to the input pin.  The media sample is not automatically memcpy'ed, and some other object may 
		// have a refcount on it and modify the buffer on another thread.  Just release the media
		// sample and get another one from the allocator.
		if (SUCCEEDED(res))
		    res = m_pVideoPin->EncodeSample(pMediaSample);
		
		printf("Sent video sample time: %lu\n", z*1000 + ulFrameCount * 33);
		HX_RELEASE(pMediaSample);
	    }
	}		
	}
	
	// Signal that all video samples have been sent
	if (m_bEncodeVideo && SUCCEEDED(res))
	{ 
	    // Create media sample
	    IHXTMediaSample* pMediaSample = NULL;
	    res = pAllocator->GetMediaSampleOfSize(0, &pMediaSample);
	    
	    // Mark the sample with ENDOFSTREAM flag
	    if (SUCCEEDED(res))
		res = pMediaSample->SetSampleFlags(HXT_SAMPLE_ENDOFSTREAM);
	    
	    // Set time
	    if (SUCCEEDED(res))
		res = pMediaSample->SetTime((z+1)*1000, (z+1)*1000);
	    
	    // Encode sample
	    if (SUCCEEDED(res))
		res = m_pVideoPin->EncodeSample(pMediaSample);
	    
	    HX_RELEASE(pMediaSample);
	}
	
	// Signal that all audio samples have been sent
	if (m_bEncodeAudio && SUCCEEDED(res))
	{ 
	    // Create media sample
	    IHXTMediaSample* pMediaSample = NULL;
	    res = pAllocator->GetMediaSampleOfSize(0, &pMediaSample);
	    
	    // Mark the sample with ENDOFSTREAM flag
	    if (SUCCEEDED(res))
		res = pMediaSample->SetSampleFlags(HXT_SAMPLE_ENDOFSTREAM);
	    
	    // Set time
	    if (SUCCEEDED(res))
		res = pMediaSample->SetTime((z+1)*1000, (z+1)*1000);
	    
	    // Encode sample
	    if (SUCCEEDED(res))
		res = m_pAudioPin->EncodeSample(pMediaSample);
	    
	    HX_RELEASE(pMediaSample);
	}
	
	// Signal that all event samples have been sent
	if (m_bEncodeEvents && SUCCEEDED(res))
	{ 
	    // Create event sample
	    IHXTEventSample* pEventSample = NULL;
	    res = m_pFactory->CreateInstance(IID_IHXTEventSample, (IUnknown**)&pEventSample);
	    
	    // Mark the sample with ENDOFSTREAM flag
	    if (SUCCEEDED(res))
		res = pEventSample->SetSampleFlags(HXT_SAMPLE_ENDOFSTREAM);
	    
	    // Set time
	    if (SUCCEEDED(res))
		res = pEventSample->SetTime(6001, 6001);
	    
	    // Encode sample
	    if (SUCCEEDED(res))
		res = m_pEventPin->EncodeSample(pEventSample);
	    
	    HX_RELEASE(pEventSample);
	}
	
	HX_RELEASE(pAllocator);
	
	
	
	return res;
}

HX_RESULT CMediaSinkEncoderApp::Shutdown()
{
    cout << "Step 12: Shutting down sdk" << endl;
    
    if (m_pFileLogObserver)
    {
	m_pFileLogObserver->Shutdown();
	HX_RELEASE(m_pFileLogObserver);
    }
    
    HX_RELEASE(m_pEventSink);
    HX_RELEASE(m_pJob);
    HX_RELEASE(m_pEventPin);
    HX_RELEASE(m_pAudioPin);
    HX_RELEASE(m_pVideoPin);
    
    // Note that the log system must be shutdown/released prior to releasing the class factory (because the
    // class factory is holding the log system DLL in memory)
    if (m_pLogSystem)
    {
	m_pLogSystem->Shutdown();
	HX_RELEASE(m_pLogSystem);
    }
    
    HX_RELEASE(m_pFactory);
    
    if (m_RmsessionDLL)
    {
#ifdef _WINDOWS
	::FreeLibrary(m_RmsessionDLL);
#elif defined _LINUX
	dlclose(m_RmsessionDLL);
#elif defined _MAC_UNIX
	CFBundleUnloadExecutable(m_RmsessionDLL);
	CFRelease(m_RmsessionDLL);
#endif
    }
    
    return HXR_OK;
}
