/* ***** 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>
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 "ihxtfileobserver.h"

#include "encoder.h"

const UINT32 MAX_BUFFER_SIZE = 255;

// Sample event sink helper class -- prints out encoding status
// Registered for events in CEncoderApp::CreateJob()
class CSampleEventSink :
public IHXTEventSink
{
public:
    CSampleEventSink() 
    {
	m_ulRefCount = 0;
    }
    
    //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):
	    {
		printf("\nEncoding started..\n");
		break;
	    }
	    
	case(eEventEncodeProgress):
	    {
		if (puValue)
		{
		    printf("Encoding %ld percent complete..\n", *puValue);
		}
		break;
	    }
	    
	case(eEventAnalysisProgress):
	    {
		if (puValue)
		    printf("Analysis %ld percent complete..\n", *puValue);
		break;
	    }
	    
	case(eEventEncodingFinished):
	    {
		printf("\nEncoding finished..\n");
		break;
	    }			
	}
	
	return HXR_OK;
    }
    
private:
    UINT32 m_ulRefCount;
};

CEncoderApp::CEncoderApp() :
m_pFactory(NULL)
,m_pJob(NULL)
,m_pFileLogObserver(NULL)
,m_pEventSink(NULL)
,m_pLogSystem(NULL)
,m_RmsessionDLL(0)
{
}


CEncoderApp::~CEncoderApp()
{
    
}

HX_RESULT CEncoderApp::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
    // Note that this is the one and only place in the Producer SDK that requires an HFS path (all other pathnames
    // on the Mac should be POSIX-style).
#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;
	}
    }
    
    // Get the HXTCreateJobFactory entry point from the encsession 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 CEncoderApp::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)?: ";		
    cin >> szQuestion1;
    
    if (szQuestion1[0] == 'y' || szQuestion1[0] == 'Y' )
    {
	cout << "  Enter log filename: ";		
	char szFile1[MAX_BUFFER_SIZE] = "";
	cin >> 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 CEncoderApp::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 CEncoderApp::SetupInput()
{
    HX_RESULT res = HXR_OK;
    
    cout << "Step 4: Setting up input" << endl;
    
    // Get input pathname
    cout << "  Enter input pathname: ";
    char szInputPathname[MAX_BUFFER_SIZE] = "";
    cin >> szInputPathname;
    
    // 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
    // Note that kPropPluginName is not set -- this allows the Producer SDK to pick the optimal
    // input reader plugin to use from the set of all input reader plugins
    if (SUCCEEDED(res))
	res = pInitParams->SetString(kPropPluginType, kValuePluginTypeInputAVFile);
    
    // Set the pathname
    if (SUCCEEDED(res))
	res = pInitParams->SetString(kPropInputPathname, szInputPathname);
    
    // 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;
    }
    
    HX_RELEASE(pInput);
    HX_RELEASE(pInitParams);
    
    return res;
}


HX_RESULT CEncoderApp::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)?: ";		
	cin >> 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)?: ";		
	cin >> 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 CEncoderApp::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] = "";
    cin >> 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 CEncoderApp::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? ";
    cin >> 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? ";
    cin >> 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 CEncoderApp::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
	{			
	    cin >> 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): ";
	cin >> ulMaxStartupLatency;
	
	// Get max time between keyframes
	UINT32 ulMaxTimeBetweenKeyframes = 0;
	cout << "  Enter max time between video keyframes in seconds (typical value is 10): ";
	cin >> 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  ? ";
	cin >> 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);
	}
    }
    
    HX_RELEASE(pOutputProfile);
    HX_RELEASE(pMediaProfile);
    HX_RELEASE(pAudienceEnum);
    
    return res;
}


HX_RESULT CEncoderApp::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)?: ";		
    cin >> szInput;
    
    // Set two-pass encode state
    if (szInput[0] == 'y' || szInput[0] == 'Y')
    {
	res = m_pJob->SetBool(kPropEnableTwoPass, 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: ";
	cin >> szInput;
    }
    
    // Set the title	
    if (SUCCEEDED(res))
	res = pMetadata->SetString(kPropTitle, szInput);
    
    // Get the author
    if (SUCCEEDED(res))
    {
	cout << "  Enter clip author: ";
	cin >> szInput;
    }
    
    // Set the author	
    if (SUCCEEDED(res))
	res = pMetadata->SetString(kPropAuthor, szInput);
    
    
    HX_RELEASE(pMetadata);
    
    return res;
}

HX_RESULT CEncoderApp::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)?: ";		
    cin >> szInput;
    
    // Serialize the job
    if (szInput[0] == 'y' || szInput[0] == 'Y')
    {
	cout << "  Enter job filename: ";		
	cin >> 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 CEncoderApp::StartEncoding()
{
    HX_RESULT res = HXR_OK;
    
    cout << "Step 11: Starting encode" << endl;
    
    // Start the encode -- this call will block until encoding has completed
    if (SUCCEEDED(res))
	res = m_pJob->StartEncoding();
    
    // 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 CEncoderApp::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);
    
    // 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;
}




