/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: audio_session-mmf.cpp,v 1.1.2.3 2005/01/28 23:37:35 liam_murray Exp $
 * 
 * Portions Copyright (c) 1995-2004 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 (the "RPSL") available at
 * http://www.helixcommunity.org/content/rpsl unless you have licensed
 * the file under the current version of the RealNetworks Community
 * Source License (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.
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL") in which case the provisions of the GPL are applicable
 * instead of those above. If you wish to allow use of your version of
 * this file only under the terms of the GPL, and not to allow others
 * to use your version of this file under the terms of either the RPSL
 * or RCSL, indicate your decision by deleting the provisions above
 * and replace them with the notice and other provisions required by
 * the GPL. If you do not delete the provisions above, a recipient may
 * use your version of this file under the terms of any one of the
 * RPSL, the RCSL or the GPL.
 * 
 * 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 ***** */

#if defined (HELIX_CONFIG_CALYPSO_AUDIO_PREF)
#include <calypso/audiopreference.h>
#endif

#include "hxcom.h"
#include "hxslist.h"
#include "hxausvc.h"
#include "hxbuffer.h"
#include "audio_session-mmf.h"
#include "hxtick.h"
#include "debug.h"
#include <e32std.h>

#define D_SYMAUDIO D_INFO

static const TInt KClientPriority = 69;
#if defined(HELIX_CONFIG_CALYPSO_AUDIO_PREF)
static const TMdaPriorityPreference KPriorityPref = (TMdaPriorityPreference)KAudioPrefComposer; 
#else
static const TMdaPriorityPreference KPriorityPref = (TMdaPriorityPreference)80; //EMdaPriorityPreferenceTime;
#endif // SERIES60_PLAYER

inline
TUint SamplesToMS(TUint count, TUint rate)
{
    HX_ASSERT(count < KMaxTInt/1000);
    HX_ASSERT(rate != 0);
    return (count * 1000) / rate;
}

#if defined(HELIX_FEATURE_DPRINTF)
// stringify constants for easier interpretation of trace output
#define MAKE_STATE_ENTRY(x) case HXSymbianAudioSession::x: return #x;
static
const char * StringifyState(HXSymbianAudioSession::State state)
{
    switch (state)
    {
        MAKE_STATE_ENTRY(CLOSED)
        MAKE_STATE_ENTRY(OPEN_PENDING)
        MAKE_STATE_ENTRY(STOPPED)
        MAKE_STATE_ENTRY(PLAYINIT_PENDING)
        MAKE_STATE_ENTRY(PLAYING)
        MAKE_STATE_ENTRY(PAUSED)
    }
    return "UNKNOWN_STATE";
}
#define MAKE_KERR_ENTRY(x) case x: return #x;

inline
const char * StringifyKErr(TInt err) 
{ 
    switch (err)
    {
        // error codes from e32std.h
        MAKE_KERR_ENTRY(KErrNone)
	    MAKE_KERR_ENTRY(KErrNotFound)
	    MAKE_KERR_ENTRY(KErrGeneral)
	    MAKE_KERR_ENTRY(KErrCancel)
	    MAKE_KERR_ENTRY(KErrNoMemory)
	    MAKE_KERR_ENTRY(KErrNotSupported)
	    MAKE_KERR_ENTRY(KErrArgument)
	    MAKE_KERR_ENTRY(KErrTotalLossOfPrecision)
	    MAKE_KERR_ENTRY(KErrBadHandle)
	    MAKE_KERR_ENTRY(KErrOverflow)
	    MAKE_KERR_ENTRY(KErrUnderflow)
	    MAKE_KERR_ENTRY(KErrAlreadyExists)
	    MAKE_KERR_ENTRY(KErrPathNotFound)
	    MAKE_KERR_ENTRY(KErrDied)
	    MAKE_KERR_ENTRY(KErrInUse)
	    MAKE_KERR_ENTRY(KErrServerTerminated)
	    MAKE_KERR_ENTRY(KErrServerBusy)
	    MAKE_KERR_ENTRY(KErrCompletion)
	    MAKE_KERR_ENTRY(KErrNotReady)
	    MAKE_KERR_ENTRY(KErrUnknown)
	    MAKE_KERR_ENTRY(KErrCorrupt)
	    MAKE_KERR_ENTRY(KErrAccessDenied)
	    MAKE_KERR_ENTRY(KErrLocked)
	    MAKE_KERR_ENTRY(KErrWrite)
	    MAKE_KERR_ENTRY(KErrDisMounted)
	    MAKE_KERR_ENTRY(KErrEof)
	    MAKE_KERR_ENTRY(KErrDiskFull)
	    MAKE_KERR_ENTRY(KErrBadDriver)
	    MAKE_KERR_ENTRY(KErrBadName)
	    MAKE_KERR_ENTRY(KErrCommsLineFail)
	    MAKE_KERR_ENTRY(KErrCommsFrame)
	    MAKE_KERR_ENTRY(KErrCommsOverrun)
	    MAKE_KERR_ENTRY(KErrCommsParity)
	    MAKE_KERR_ENTRY(KErrTimedOut)
	    MAKE_KERR_ENTRY(KErrCouldNotConnect)
	    MAKE_KERR_ENTRY(KErrCouldNotDisconnect)
	    MAKE_KERR_ENTRY(KErrDisconnected)
	    MAKE_KERR_ENTRY(KErrBadLibraryEntryPoint)
	    MAKE_KERR_ENTRY(KErrBadDescriptor)
	    MAKE_KERR_ENTRY(KErrAbort)
	    MAKE_KERR_ENTRY(KErrTooBig)
	    MAKE_KERR_ENTRY(KErrDivideByZero)
	    MAKE_KERR_ENTRY(KErrBadPower)
	    MAKE_KERR_ENTRY(KErrDirFull)
	    MAKE_KERR_ENTRY(KErrHardwareNotAvailable)
	    MAKE_KERR_ENTRY(KErrSessionClosed)
	    MAKE_KERR_ENTRY(KErrPermissionDenied)
    }
    return "{e32std error}";
}

#else
// do nothing (compile out) when !HELIX_FEATURE_DPRINTF
inline 
const char * StringifyState(HXSymbianAudioSession::State state) { return 0; }
inline
const char * StringifyKErr(TInt err) { return 0; }
#endif

static TInt FlagToNumber(TMMFSampleRate flag)
{
    switch( flag )
    {
    case EMMFSampleRate8000Hz:
        return 8000;
    case EMMFSampleRate11025Hz:
        return 11025;
    case EMMFSampleRate16000Hz:
        return 16000;
    case EMMFSampleRate22050Hz:
        return 22050;
    case EMMFSampleRate32000Hz:
        return 32000;
    case EMMFSampleRate44100Hz:
        return 44100;
    case EMMFSampleRate48000Hz:
        return 48000;
    default:
        break;
    }
    HX_ASSERT(false);
    return 0;
}

static TMMFSampleRate NumberToFlag(TInt num)
{
    switch(num)
    {
    case 8000:
        return  EMMFSampleRate8000Hz;
    case 11025:
        return  EMMFSampleRate11025Hz;
    case 16000:
        return  EMMFSampleRate16000Hz;
    case 22050:
        return  EMMFSampleRate22050Hz;
    case 32000:
        return  EMMFSampleRate32000Hz;
    case 44100:
        return  EMMFSampleRate44100Hz;
    case 48000:
        return  EMMFSampleRate48000Hz;
    default:
        break;
    }
    HX_ASSERT(false);
    return EMMFSampleRate16000Hz;
}

HXSymbianAudioSession::HXSymbianAudioSession(RThread& client,
                                             HXSymbianAudioServer* pServer)
:   CSession(client),
    m_pServer(pServer),
    m_wantsNotify(false),
    m_lastPlayError(KErrNone),
    m_pStream(NULL),
    m_pPendingFillBuffer(NULL),
    m_state(CLOSED),
    m_sampleRate(0),
    m_cbFrontBufferWritten(0),
    m_cbSample(0),
    m_samplesWritten(0),
    m_baseSampleCount(0),
    m_unplayedSampleCount(0),
    m_msTimePlayed(0),
    m_sampleCountResetPending(false),
    m_msReInitTimePlayed(0)
{
    // add the session to the server
    HX_ASSERT(m_pServer);
    m_pServer->AddSession();
    memset(&m_Settings, 0, sizeof( m_Settings) );
}

//
// HXSymbianAudioSession::dtor
//
HXSymbianAudioSession::~HXSymbianAudioSession()
{
    Trans(CLOSED);
    m_pServer->DelSession();
}


//
// HXSymbianAudioSession::NewL
//
// creates a new session
//
HXSymbianAudioSession* HXSymbianAudioSession::NewL(RThread& client,
                                                   HXSymbianAudioServer* pServer)
{
    return new (ELeave) HXSymbianAudioSession(client, pServer);
}

//
// HXSymbianAudioSession::ServiceL
//
// services a client message
//
void HXSymbianAudioSession::ServiceL(const RMessage& mesg)
{
    switch (mesg.Function()) 
    {
    case HXSymbianAudioServer::EAS_Init:
        Init();
        break;
    case HXSymbianAudioServer::EAS_Play:
        Play();
        break;
    case HXSymbianAudioServer::EAS_Pause:
        Pause();
        break;
    case HXSymbianAudioServer::EAS_Write:
        Write();
        break;
    case HXSymbianAudioServer::EAS_CancelWrite:
        mesg.Complete(KErrNone);
        break;
    case HXSymbianAudioServer::EAS_GetTime:
        GetTime();
        break;
    case HXSymbianAudioServer::EAS_GetBlocksBuffered:
        GetBlocksBuffered();
        break;
    case HXSymbianAudioServer::EAS_SetVolume:
        SetVolume();
        break;
    case HXSymbianAudioServer::EAS_GetVolume:
        GetVolume();
        break;
    case HXSymbianAudioServer::EAS_GetMaxVolume:
        GetMaxVolume();
        break;
    case HXSymbianAudioServer::EAS_GetMinVolume:
        GetMinVolume();
        break;
    case HXSymbianAudioServer::EAS_Stop:
        Stop();
        break;
    case HXSymbianAudioServer::EAS_RequestDeviceTakenNotification:
        RequestDeviceTakenNotification();
        break;
    case HXSymbianAudioServer::EAS_CancelDeviceTakenNotification:
        CancelDeviceTakenNotification();
        break;
    default:
        mesg.Complete(KErrNotSupported);
        break;
    }
}

//
// HXSymbianAudioSession::Init
//
// 1. Matches input sample rate to output sample rate
//    by building a sample rate converter, if necessary.
// 2. Opens and initializes the audio device.
//
void HXSymbianAudioSession::Init()
{
    TInt err = KErrNone;

    // we always do 16 bit samples
    const TUint bitsPerSample   = 16;
    m_sampleRate                = Message().Int0();
    TUint channelCount          = Message().Int1();
    m_cbSample                  = channelCount * (bitsPerSample / 8);

    // translate the audio props to flags needed by interface
    m_Settings.iRate     = NumberToFlag(m_sampleRate);
    m_Settings.iChannels = channelCount;
    m_Settings.iEncoding = EMMFSoundEncoding16BitPCM;

    DPRINTF(D_SYMAUDIO, ("HXSymbianAudioSession::Init(): rate = %ld; chan = %ld; sample frame = %lu bytes\n", m_sampleRate, m_Settings.iChannels, m_cbSample));
    
    switch (m_state)
    {
    case STOPPED:
        DPRINTF(D_SYMAUDIO, ("HXSymbianAudioSession::Init(): calling SetConfgL() with settings\n"));
        TRAP(err, (m_pStream->CMMFDevSound::SetConfigL(m_Settings)));
        Message().Complete(err);
        break;
    case CLOSED:
        Trans(OPEN_PENDING);
        HX_ASSERT(!m_pStream);
        TRAP(err, (m_pStream = CMMFDevSound::NewL()));
        if (KErrNone == err)
        {
            DPRINTF(D_SYMAUDIO, ("HXSymbianAudioSession::Init(): calling InitializeL for newly allocated dev sound\n"));
            m_InitMessage = Message();
            TRAP(err, (m_pStream->InitializeL(*this, EMMFStatePlaying)));
        }
        if (KErrNone != err)
        {
            Trans(CLOSED);
            Message().Complete(err);
        }
        break;
    default:
        HX_ASSERT(false);
        Message().Complete(KErrGeneral);
        break;
    }
}

//
// HXSymbianAudioSession::Play
//
void HXSymbianAudioSession::Play()
{
    DPRINTF(D_SYMAUDIO, ("HXSymbianAudioSession::Play(): state = %s\n", StringifyState(m_state) ));
    TInt err = KErrNone;

    switch (m_state)
    {
    case STOPPED:
        DoPlayInit();
        break;
    case PAUSED:
        HX_ASSERT(m_pStream);
        Trans(PLAYING);
        m_pStream->PlayData();
        break;
    case PLAYING:
    case PLAYINIT_PENDING:
        // do nothing
        break;
    default:
        // unexpected
        HX_ASSERT(false);
        err = KErrGeneral;
        break;
    }

    Message().Complete(err);
}

//
// HXSymbianAudioSession::Pause
//
void HXSymbianAudioSession::Pause()
{
    DPRINTF(D_SYMAUDIO, ("HXSymbianAudioSession::Pause(): state = %s\n", StringifyState(m_state)));
    TInt err = KErrNone;

    switch (m_state)
    {
    case PLAYING:
        Trans(PAUSED);
        HX_ASSERT(m_pStream);
        m_pStream->Pause();
        break;
    case STOPPED:
        // note: pause called immediately after init by higher level code (fall through)
    case PAUSED:
        // do nothing
        break;
    default:
        HX_ASSERT(false);
        err = KErrGeneral;
        break;
    }

    Message().Complete(err);
}

//
// HXSymbianAudioSession::Write
//
//
void HXSymbianAudioSession::Write()
{
    TInt err = KErrNone;
    
    switch (m_state)
    {
    case STOPPED:
        DPRINTF(D_SYMAUDIO, ("HXSymbianAudioSession::Write(): STOPPED; last err = %s\n", StringifyKErr(m_lastPlayError)));
        if (m_lastPlayError != KErrNone)
        {
            // we are stopped because of we got a call to PlayError
            err = m_lastPlayError;
            break;
        } 
        // fall through if no error
    case PLAYING:
    case PAUSED:
    {
        IHXBuffer* pAudioBuf = (IHXBuffer*)Message().Ptr0();
        if (pAudioBuf)
        {
            if (!m_bufferList.AddTail(pAudioBuf))
            {
                err = KErrNoMemory;
            }
        }
        else
        {
            err = KErrArgument;
        }
        break;
    }
    default:
        DPRINTF(D_SYMAUDIO, ("HXSymbianAudioSession::Write(): called in unexpected state %s \n", StringifyState(m_state)));
        HX_ASSERT(false);
        err = KErrGeneral;
        break;
    }

    Message().Complete(err);
    
    if( KErrNone == err && m_pPendingFillBuffer )
    {
        DPRINTF(D_SYMAUDIO, ("HXSymbianAudioSession::Write(): calling BufferToBeFilled\n"));
        BufferToBeFilled(m_pPendingFillBuffer);
        HX_ASSERT(!m_pPendingFillBuffer);
    }
}

// calculate diff from 'base' to 'current'
inline
TUint WrapDiff(TUint base, TUint current)
{
    if (base <= current)
    {
        return current - base;
    }

    // assume 'current' wrapped around
    return KMaxTUint - base + current + 1;
}

void HXSymbianAudioSession::OnResetSampleCount()
{
    m_sampleCountResetPending   = false;
    m_baseSampleCount           = 0;
    m_unplayedSampleCount       = 0;
    // restore time in case this was auto re-init after underflow or similar
    m_msTimePlayed              = m_msReInitTimePlayed;
    m_msReInitTimePlayed        = 0;
}

void HXSymbianAudioSession::CheckSampleCountReset(TUint sampleCount)
{
    // After we stop the audio stream (seek case) or get an underflow error we
    // reinitialize the audio device by calling PlayInitL(). At some point 
    // thereafter the value returned by SamplesPlayed() is reset. That point is
    // not predictable. In some cases it is reset by the time we get the next call
    // to BufferToBeFilled(), but not always. To deal with this we set a flag when
    // we call PlayInitL() and wait for the samples played value to fall below the
    // last known base value. We then assume this is because the sample counter has
    // been reset.
   
    if (m_sampleCountResetPending)
    {
        DPRINTF(D_SYMAUDIO, ("HXSymbianAudioSession::HandleSampleCountReset(): checking for reset (old base = %lu;  samps played = %lu; samps written = %lu)\n", m_baseSampleCount, sampleCount, m_samplesWritten));
        if ( sampleCount < m_baseSampleCount || 0 == m_baseSampleCount /*0 = first play*/)
        {
            DPRINTF(D_SYMAUDIO, ("HXSymbianAudioSession::HandleSampleCountReset(): resetting base sample count\n"));
            OnResetSampleCount();
        }
        else
        {
            if (m_unplayedSampleCount > 0 && 
                sampleCount - m_baseSampleCount > m_unplayedSampleCount)
            {
                // Special case:
                //
                // If the base value at the time of the re-init is small, there is a risk that 
                // the counter is reset but by the time we see the (post-reset) value it has
                // exceeded the base value. That breaks the logic above. We deal with this by
                // comparing the samples played with the number of unwritten samples at the time
                // of the underflow/stop. If the value is greater it must be from newly written
                // samples.
                DPRINTF(D_SYMAUDIO, ("HXSymbianAudioSession::HandleSampleCountReset(): resetting base sample count (special case %lu > %lu)\n", sampleCount - m_baseSampleCount, m_unplayedSampleCount));
                OnResetSampleCount();
            }
        }
    }
}


void HXSymbianAudioSession::UpdateTimeSampleStats()
{
    // determine how much time has elapsed since last time computation
    TUint sampleCount = m_pStream->SamplesPlayed();
   
    CheckSampleCountReset(sampleCount);
    if (m_sampleCountResetPending)
    { 
        // don't update time; we only want to add time for samples played since stop
        return;
    }

    HX_ASSERT(m_samplesWritten >= sampleCount);
    m_unplayedSampleCount = m_samplesWritten - sampleCount;

    // assert likely indicates error; wrap-around is rare case (occurs after ~27 hours for 44Khz)
    HX_ASSERT(m_baseSampleCount <= sampleCount);
    TUint samplesElapsed = WrapDiff(m_baseSampleCount, sampleCount);

    // convert samples-played to time-played
    TUint msElapsed = SamplesToMS(samplesElapsed, m_sampleRate);

     // update time
    m_msTimePlayed += msElapsed;	

    DPRINTF(D_INFO, ("HXSymbianAudioSession::UpdateTimeSampleStats(): base = %lu; played = %lu; (unplayed = %lu)\n", m_baseSampleCount, sampleCount, m_unplayedSampleCount));
    DPRINTF(D_INFO, ("HXSymbianAudioSession::UpdateTimeSampleStats(): adding %lu ms; total now %lu ms\n", msElapsed, m_msTimePlayed));

    // update base sample count for next time
    m_baseSampleCount = sampleCount;
}
//
// HXSymbianAudioSession::GetTime
//
void HXSymbianAudioSession::GetTime()
{
    UpdateTimeSampleStats();
    Message().Complete(m_msTimePlayed);
}


//
// HXSymbianAudioSession::GetBlocksBuffered
//
// Return the number of blocks buffered by this object.
//
void HXSymbianAudioSession::GetBlocksBuffered()
{
    TInt nTmp = m_bufferList.GetCount();
    Message().Complete(nTmp);
}

//
// HXSymbianAudioSession::SetVolume
//
// set the volume -- convert from 0-100 to 0-max range
//
void HXSymbianAudioSession::SetVolume()
{
    if (m_pStream)
    {
        m_pStream->SetVolume(Message().Int0());
    }
    Message().Complete(0);
}

//
// HXSymbianAudioSession::GetVolume
//
// get the current volume normalized to 0-100 range
//
void HXSymbianAudioSession::GetVolume()
{
    TInt vol = 0;
    if (m_pStream)
    {
        vol = m_pStream->Volume();
    }
    Message().Complete(vol);
}

//
// HXSymbianAudioSession::GetMaxVolume
//
// get the maxium device volume
//
void HXSymbianAudioSession::GetMaxVolume()
{
    TInt maxVol = 0;
    if (m_pStream)
    {
        maxVol = m_pStream->MaxVolume();
    }

    Message().Complete(maxVol);
}

//
// HXSymbianAudioSession::GetMinVolume
//
// get the minimum device volume
//
void HXSymbianAudioSession::GetMinVolume()
{
    Message().Complete(0);
}


//
// HXSymbianAudioSession::Stop
//
//
void HXSymbianAudioSession::Stop()
{
    if (PLAYING == m_state || PAUSED == m_state || PLAYINIT_PENDING == m_state)
    {
        // important: update stats before stopping
        UpdateTimeSampleStats();
        DPRINTF(D_SYMAUDIO, ("HXSymbianAudioSession::Stop(): %lu ms unplayed in audio device\n", SamplesToMS(m_unplayedSampleCount, m_sampleRate)));
       
        Trans(STOPPED);
        HX_ASSERT(m_pStream);
        m_pStream->Stop();
        while (!m_bufferList.IsEmpty())
        {
            IHXBuffer* pBuf = (IHXBuffer*)m_bufferList.RemoveHead();
            HX_RELEASE(pBuf);
        }
        m_cbFrontBufferWritten = 0; 
    }

    Message().Complete(KErrNone);
}

void
HXSymbianAudioSession::RequestDeviceTakenNotification()
{
    DPRINTF(D_INFO, ("HXSymbianAudioSession::RequestDeviceTakenNotification()\n"));
    m_wantsNotify = true;
    m_notifyRequest = Message();
}

void
HXSymbianAudioSession::CancelDeviceTakenNotification()
{
    if (m_wantsNotify)
    {
        m_notifyRequest.Complete(KErrCancel);
        m_wantsNotify = false;
    }
}

//
// HXSymbianAudioSession::NotifyDeviceTaken
//
// notify the client that the audio device has been taken if a
// notification requrest has been made 
//
void HXSymbianAudioSession::NotifyDeviceTaken()
{
    if (m_wantsNotify)
    {
        DPRINTF(D_INFO, ("HXSymbianAudioSession::NotifyDeviceTaken(): doing notify...\n"));
        m_notifyRequest.Complete(m_lastPlayError);
        m_wantsNotify = false;
    }
}



void HXSymbianAudioSession::Trans(HXSymbianAudioSession::State state)
{
    if (state != m_state)
    {
        DPRINTF(D_SYMAUDIO, ("HXSymbianAudioSession::Trans(): %s -> %s\n", StringifyState(m_state), StringifyState(state)));
        m_state = state;
        switch (m_state)
        {
        case STOPPED:
            InitStoppedState();
            break;
        case PLAYINIT_PENDING:
            InitPlayInitPendingState();
            break;
        case CLOSED:
            InitClosedState();
            break;
        default:
            // nothing
            break;
        }   
    }
}

void HXSymbianAudioSession::InitPlayInitPendingState()
{
    HX_ASSERT(PLAYINIT_PENDING == m_state);
    m_lastPlayError = KErrNone;
}

void HXSymbianAudioSession::InitStoppedState()
{
    HX_ASSERT (STOPPED == m_state);
    m_msTimePlayed                   = 0;
    m_samplesWritten                 = 0;
    m_pPendingFillBuffer             = NULL;

    // we leave some time-associated values as is until sample count reset occurs
    m_sampleCountResetPending        = true;
}

void HXSymbianAudioSession::InitClosedState()
{
    HX_ASSERT(CLOSED == m_state);
    while (!m_bufferList.IsEmpty())
    {
        IHXBuffer* pBuf = (IHXBuffer*)m_bufferList.RemoveHead();
        HX_RELEASE(pBuf);
    }

    if (m_wantsNotify)
    {
        m_notifyRequest.Complete(KErrCancel);
    }

    HX_DELETE(m_pStream);
}
    
void HXSymbianAudioSession::DoPlayInit()
{
    DPRINTF(D_SYMAUDIO, ("HXSymbianAudioSession::DoPlayInit(): setting priority and calling PlayInitL\n"));
    
    HX_ASSERT(STOPPED == m_state);
    Trans(PLAYINIT_PENDING);

    // set priority.
    TMMFPrioritySettings prioritySettings;
    prioritySettings.iPref     = KPriorityPref;
    prioritySettings.iPriority = KClientPriority;
    prioritySettings.iState    = EMMFStatePlaying;
    m_pStream->SetPrioritySettings(prioritySettings);

    TRAPD(err, m_pStream->PlayInitL());
    DPRINTF(D_SYMAUDIO, ("HXSymbianAudioSession::DoPlayInit(): PlayInitL() result: %s\n", StringifyKErr(err)));
    if (err != KErrNone)
    {
        Trans(STOPPED); 
    }
}


// MDevSoundObserver
void HXSymbianAudioSession::InitializeComplete(TInt aError)
{
    DPRINTF(D_SYMAUDIO, ("HXSymbianAudioSession::InitializeComplete(): err = %s\n", StringifyKErr(aError)));
    TRAPD(err, (m_pStream->CMMFDevSound::SetConfigL(m_Settings)));
    Trans((KErrNone == err) ? STOPPED : CLOSED);
    DPRINTF(D_SYMAUDIO, ("HXSymbianAudioSession::InitializeComplete() SetConfgL(): err = %s\n", StringifyKErr(err)));
    m_InitMessage.Complete(err);
}

// MDevSoundObserver  
// Called whenever the media server needs more data to play, or when we have more audio
// data added and a previous fill request was not fullfilled.
void HXSymbianAudioSession::BufferToBeFilled(CMMFBuffer* aBuffer)
{
    // we are now actively playing
    switch (m_state)
    {
    case PLAYINIT_PENDING:
        Trans(PLAYING);
        break;
    case PAUSED:
    case STOPPED:
    default:
        DPRINTF(D_SYMAUDIO, ("HXSymbianAudioSession::BufferToBeFilled(): unexpected state %s\n", StringifyState(m_state)));
    case PLAYING:
        break;
    }
    
    if( aBuffer )
    {
        TDes8&  dataDesc  = ((CMMFDataBuffer*)aBuffer)->Data();
        dataDesc = TPtrC8(NULL, 0 ); // ensure descriptor is reset/clear
        TUint cbDest  = aBuffer->RequestSize();
        
        //DPRINTF(D_SYMAUDIO, ("HXSymbianAudioSession::BufferToBeFilled(): req size = %ld; dest buffer size = %lu; desc max = %lu; buffer list count = %ld\n", aBuffer->RequestSize(), aBuffer->BufferSize(), dataDesc.MaxSize(), m_bufferList.GetCount()));

        while ( !m_bufferList.IsEmpty() && cbDest >= m_cbSample)
        {
            // get buffer at front
            IHXBuffer* pBuffer = (IHXBuffer*)m_bufferList.GetHead();
            HX_ASSERT(pBuffer);
            UINT32 cbBuffer = pBuffer->GetSize();
            
            // check buffer length
            HX_ASSERT(cbBuffer > 0);
            HX_ASSERT(cbBuffer <= KMaxTInt);

            // m_cbFrontBufferWritten = bytes already written from front buffer
            HX_ASSERT(m_cbFrontBufferWritten < cbBuffer);
            
            //DPRINTF(D_SYMAUDIO, ("HXSymbianAudioSession::BufferToBeFilled(): next src buffer size = %lu; front buffer written = %lu\n", cbBuffer, m_cbFrontBufferWritten));

            // decide how much to write; we may not be able to write a full buffer
            UINT32 cbToWrite = pBuffer->GetSize() - m_cbFrontBufferWritten;
            if (cbToWrite > UINT32(cbDest))
            {
                // limit amount to destination buffer space available
                cbToWrite = cbDest;
            }

            // probably overkill: round write amount so we write only full sample (src buffers assumed to be frame-aligned)
            HX_ASSERT(cbToWrite % m_cbSample == 0);
            cbToWrite = (cbToWrite/m_cbSample) * m_cbSample;
            HX_ASSERT(cbToWrite != 0);
            HX_ASSERT(cbToWrite % m_cbSample == 0);
                
            // copy
            TPtrC8 desc((TUint8*)pBuffer->GetBuffer() + m_cbFrontBufferWritten, TInt(cbToWrite));
            dataDesc.Append(desc);
            m_cbFrontBufferWritten += cbToWrite;
            cbDest -= cbToWrite;

            // keep track of how many full samples we write
            HX_ASSERT(0 == (cbToWrite % m_cbSample)); 
            m_samplesWritten += (cbToWrite / m_cbSample);

            if (m_cbFrontBufferWritten == cbBuffer)
            {
                // we used up the front buffer; toss it
                IHXBuffer* pTmp = (IHXBuffer*)m_bufferList.RemoveHead();
                HX_RELEASE(pTmp);
                m_cbFrontBufferWritten = 0;
            }
        }

        if (dataDesc.Length() > 0)
        {
            DPRINTF(D_SYMAUDIO, ("HXSymbianAudioSession::BufferToBeFilled(): play buff = %ld bytes; samps written = %lu\n", dataDesc.Length(), m_samplesWritten));
            if (PLAYING == m_state)
            {
                m_pStream->PlayData();
            }
            m_pPendingFillBuffer = NULL;
        }
        else
        {
            DPRINTF(D_SYMAUDIO, ("HXSymbianAudioSession::BufferToBeFilled(): unable to fill buffer (no src data)\n"));
            // hold on to buffer; we'll fill it once we have more src data
            m_pPendingFillBuffer = aBuffer;
        }
    }    
}

// MDevSoundObserver
void HXSymbianAudioSession::PlayError(TInt aError)
{
    DPRINTF(D_SYMAUDIO, ("HXSymbianAudioSession::PlayError(): err = %s\n", StringifyKErr(aError)));
    if (aError != KErrNone)
    {
        // update stats before stopping
        UpdateTimeSampleStats();

        // stream is implicitly stopped when an error occurs
        switch (aError)
        {
        case KErrUnderflow:
            {
                // either we just played out or (more likely) we were not
                // writing to the device fast enough

                // add unwritten (lost) samples to time played
                TUint msDiscarded = SamplesToMS(m_unplayedSampleCount, m_sampleRate);
                DPRINTF(D_SYMAUDIO, ("HXSymbianAudioSession::PlayError(): underflow discarded time = %lu\n", msDiscarded));
                m_msTimePlayed += msDiscarded;
                // save current time so we preserve monotonically increasing time
                m_msReInitTimePlayed = m_msTimePlayed;
                Trans(STOPPED);
                // re-init so we resume (continue) audio playback
                DoPlayInit();
            }
            break;
        case KErrCancel:
            {
                Trans(STOPPED);
            }
            break;
        case KErrAccessDenied: 
        case KErrInUse:
        case KErrDied: // incoming message notification on 6630 generates this
        default:
            {
                m_lastPlayError = aError;
                Trans(STOPPED);
                NotifyDeviceTaken();
            }
            break;
        }
    }
}

//These are never used in PCM playback, just recording or converting.
void HXSymbianAudioSession::ToneFinished(TInt aError) {}
void HXSymbianAudioSession::RecordError(TInt aError)  {}
void HXSymbianAudioSession::ConvertError(TInt aError) {}
void HXSymbianAudioSession::BufferToBeEmptied(CMMFBuffer* aBuffer) {}
void HXSymbianAudioSession::DeviceMessage(TUid aMessageType, const TDesC8& aMsg) {}
void HXSymbianAudioSession::SendEventToClient(const TMMFEvent& aEvent) {}









