diff -r 000000000000 -r bde4ae8d615e os/mm/devsound/sounddevbt/src/A2dpBlueTooth/headsetaudioif/GavdpStateMachine.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/os/mm/devsound/sounddevbt/src/A2dpBlueTooth/headsetaudioif/GavdpStateMachine.cpp Fri Jun 15 03:10:57 2012 +0200 @@ -0,0 +1,1747 @@ +// Copyright (c) 2005-2009 Nokia Corporation and/or its subsidiary(-ies). +// All rights reserved. +// This component and the accompanying materials are made available +// under the terms of "Eclipse Public License v1.0" +// which accompanies this distribution, and is available +// at the URL "http://www.eclipse.org/legal/epl-v10.html". +// +// Initial Contributors: +// Nokia Corporation - initial contribution. +// +// Contributors: +// +// Description: +// + +#include "A2dpCodecUtilities.h" +#include "GavdpStateMachine.h" + + +/** +GAVDP State Machine Panics +**/ +enum TGavdpStateMachinePanic + { + EGavdpStateMachineBadState,//0 + EGavdpStateMachineBadConfigByRemoteHeadsetState,//1 + EGavdpStateMachineBadBTAddr,//2 + EGavdpStateMachineUnexpectedCallback,//3 + EGavdpStateMachineDataTypeMismatch//4 + }; + + +static void Panic(TGavdpStateMachinePanic aPanic) +// Panic client + { + _LIT(KGavdpStateMachinePanicName, "GAVDP State Mchn"); + User::Panic(KGavdpStateMachinePanicName, aPanic); + } + + +/** +Utility function to map a bluetooth code to a standard SymbianOS code +Where a standard Symbian error code exists the error is mapped +to the standard code. Ideally all HCI, L2CAP, AVDTP, GAVDP errors +should be mapped to standard SymbianOS codes since bluetooth specific +error codes aren't meaningful in the multimedia domain +*/ +static TInt ConvertToStandardSymbianOSError(TInt aError) + { + TInt error; + + switch (aError) + { + case KHCIErrorBase-EPageTimedOut://-6004 + error = KErrCouldNotConnect; + break; + case KHCIErrorBase-EAuthenticationFailure://-6005 + error = KErrCouldNotConnect; + break; + case KHCIErrorBase-EPairingNotAllowed: //-6024 + error = KErrCouldNotConnect; + break; + case KHCIErrorBase-ELMPResponseTimeout: //-6034 + error = KErrTimedOut; + break; + case KErrHCIConnectFailed: //-6304 + error = KErrCouldNotConnect; + break; + case KErrHCILinkDisconnection: //-6305 + error = KErrDisconnected; + break; + default: + error = aError; + break; + } + return error; + } + + +/** +Creates CGAVDPStateMachine + +@internalComponent +@return CGAVDPStateMachine* +*/ +CGAVDPStateMachine* CGAVDPStateMachine::NewL(MGAVDPStateChangeObserver& aGAVDPStateChangeObserver, CA2dpAudioCodecConfiguration& aA2dpCodecSettings, RSocketServ& aSocketServer) + { + CGAVDPStateMachine* self = new(ELeave) CGAVDPStateMachine(aGAVDPStateChangeObserver, aA2dpCodecSettings, aSocketServer); + CleanupStack::PushL(self); + self->ConstructL(); + CleanupStack::Pop(self); + return self; + } + + +/** +Constructor +[TODO] change default stereo mode to EMMFJoint when hardware has sufficient bandwidth +Note we make the AO priority one less than normal to give the AOs in GAVDP +preference +*/ +CGAVDPStateMachine::CGAVDPStateMachine(MGAVDPStateChangeObserver& aGAVDPStateChangeObserver, CA2dpAudioCodecConfiguration& aA2dpCodecSettings, RSocketServ& aSocketServer) : CActive(EPriorityNormal-1), +iGAVDPStateChangeObserver(aGAVDPStateChangeObserver) ,iA2dpCodecSettings(aA2dpCodecSettings), iSocketServer(aSocketServer) + { + CActiveScheduler::Add(this); + } + + +void CGAVDPStateMachine::ConstructL() + { + User::LeaveIfError(iGavdp.Open(*this, iSocketServer)); + User::LeaveIfError(RegisterLocalSEP()); + iSignallingTransactionTimeout = CGavdpTimeout::NewL(*this); + } + + +/** +Internal function to register local SEPs with GAVDP +The function sets the iSymbianDeviceSEID to the TSEID +of the Symbian device +*/ +TInt CGAVDPStateMachine::RegisterLocalSEP() + { + //register SEPs - currently SBC & mp3 SEPs are supported + TAvdtpSEPInfo avdtpSEPInfo; + avdtpSEPInfo.SetInUse(EFalse); + avdtpSEPInfo.SetMediaType(EAvdtpMediaTypeAudio); + avdtpSEPInfo.SetIsSink(EFalse); + TInt err = iGavdp.RegisterSEP(avdtpSEPInfo); + TUsableSEP sep; + sep.iSEID = avdtpSEPInfo.SEID(); + sep.iDataType = KMMFFourCCCodeSBC; + err = iSymbianDeviceSEPs.Append(sep); + + if (!err) + { + avdtpSEPInfo.SetInUse(EFalse); + avdtpSEPInfo.SetMediaType(EAvdtpMediaTypeAudio); + avdtpSEPInfo.SetIsSink(EFalse); + err = iGavdp.RegisterSEP(avdtpSEPInfo); + if (!err) + { + sep.iSEID = avdtpSEPInfo.SEID(); + sep.iDataType = KMMFFourCCCodeMP3; + err = iSymbianDeviceSEPs.Append(sep); + } + } + if (!err) + { + TBool SEPDataTypeFound(EFalse); + for(TUint i = 0; i< iSymbianDeviceSEPs.Count();i++) + { + if (iSymbianDeviceSEPs[i].iDataType == iA2dpCodecSettings.HeadsetCodecDataType()) + { + iSymbianDeviceSEID = iSymbianDeviceSEPs[i].iSEID; + SEPDataTypeFound = ETrue; + break; + } + } + if (!SEPDataTypeFound) + { + err = KErrNotFound; + } + } + return err; + } + + +/** +Standard destructor. +*/ +CGAVDPStateMachine::~CGAVDPStateMachine() + { + Cancel(); + iBearerSocket.Close(); + iGavdp.Close(); + iPotentialSEPs.Close(); + iUsableSEPs.Close(); + iSymbianDeviceSEPs.Close(); + iSEPCapabilities.ResetAndDestroy(); + iSEPCapabilities.Close(); + delete iSignallingTransactionTimeout; + } + + +/** +Function to set the Bluetooth address of the headset. + +@param aRemoteAddress Bluetooth address of the headset +*/ +void CGAVDPStateMachine::SetBTAddress(const TBTDevAddr& aRemoteAddress) + { + if (iBTDevAddr != aRemoteAddress) + {//must be using a new headet so reset state machine + Reset(); + iBTDevAddr = aRemoteAddress; + } + } + + +/** +Function to returns the current state of the GAVDP state machine. + +@return the current state of the GAVDP state machine +*/ +TGAVDPState CGAVDPStateMachine::State() const + { + return iCurrentState ; + } + + +/** +Internal function to resets the GAVDP state machine to the EGAVDPIdle state +Note that if the headset is already connected Reseting +will cause a subsequent call to RGavdp.Connect, however +if we are already connected this should complete straight away + +Note we don't close the bearer socket here as we need to ensure +this is closed after the RTP streamer is deleted so this is handled by the CA2dpBTHeadsetAudioInterface +*/ +void CGAVDPStateMachine::Reset() + { + iPotentialSEPs.Reset(); + iUsableSEPs.Reset(); + iSEPCapabilities.ResetAndDestroy(); + iSEPIterator = 0; + iSEPCapabilityEntry = 0; + iA2dpCodecSettings.Reset(); + iInitialState = TGAVDPState::EGAVDPIdle; + iCurrentState = TGAVDPState::EGAVDPIdle; + iNextState = TGAVDPState::EGAVDPIdle; + iTargetState = TGAVDPState::EGAVDPIdle; + iConfigurationByRemoteHeadsetState.Reset(); + iHeadsetSEID.Reset(); + iLocalSEPConfigured = EFalse; + iChangeOfSelectedHeadsetSEP = EFalse; + iGavdp.Cancel(); + iSignallingTransactionTimeout->Cancel(); + } + + +/** +Function to move the GAVDP state machine to the state specified in aNewState +Not all state changes are allowed, however. +Permitted state changes are anystate to ->EGAVDPIdle,EConnectedToGavdp,ESEPsDiscovered +which causes a reset. +Anything prior to EGAVDPOpen->EGAVDPOpen +EGAVDPOpen->EGAVDPStreaming +EGAVDPStreaming->EGAVDPOpen + +When the state change is completed a GAVDPStateChangeComplete callback is +made on the MGAVDPStateChangeObserver, which is the CA2dpBTHeadsetAudioInterface + +@param aNewState The state we wish to go to +@return standard SymbianOS error code +*/ +TInt CGAVDPStateMachine::ChangeState(const TGAVDPState& aNewState) + { + if (iStateChangeInProgress) + {//can't change state if a state change is already in progress + return KErrNotReady; + } + if (aNewState.State()<= TGAVDPState::ESEPSelected) + { + //if the state is ESEPSelected or less then + //the assumption is we need to reselect the SEP and in + //effect redo the entire initialization prior to selecting the SEP + //therefore roll back to EGAVDPIdle and reset. + Reset(); + } + else if (aNewState == TGAVDPState::EGAVDPStreaming) + { + //must be in the EGAVDPOpen state in order to go to EGAVDPStreaming + if (iCurrentState != TGAVDPState::EGAVDPOpen) + { + return KErrNotReady; + } + } + + if (aNewState == iCurrentState) + { + //then we are already in the requested state so don't move to + //next state- so just call the RunL and complete + iStateChangeInProgress = EFalse;//we're not going to change the state + TRequestStatus* stat = &iStatus; + if (!IsActive()) + { + User::RequestComplete(stat, KErrNone); + SetActive(); + } + } + else + {//need to move to the next state + iInitialState = iCurrentState; //incase we need to wind back if state change fails + iTargetState = aNewState; + NextState(); + } + return KErrNone; + } + + +/** +Function to cause a reconfiguration of the SEP. The function determines +whether a reconfiguration is required ie becuase the settings have changed. +And if so the SEP is reconfigured or a new SEP is selected if the reconfiguration +results in a change of SEP ie because the datatype has changed +This function makes a GAVDPStateChangeComplete callback on the CA2dpBTHeadsetAudioInterface +If the SEP changes because of a change in data type then the iChangeOfSelectedHeadsetSEP +flag is set which is used by the state machine to cause the media transport +caps to be added to the SEP + +@param aSettingsHaveChanged set to ETrue if the settings have changed + +@return SymbianOS error code +*/ +TInt CGAVDPStateMachine::Reconfigure(TBool aSettingsHaveChanged) + { + //not allowed to reconfigure in the middle of a state change + if ((iStateChangeInProgress)||(IsActive())) + { + return KErrNotReady; + } + + //in order to perform a reconfigure the current state must + //be either EConfigured or EGAVDPOpen + //reconfiguration by the SymbianOs device in the streaming state is not supported + //in this implementation even though it is allowed by the GAVDP profile + if ((iCurrentState != TGAVDPState::EConfigured) && + (iCurrentState != TGAVDPState::EGAVDPOpen)) + { + return KErrNotReady; + } + + __ASSERT_DEBUG((iCurrentState == iTargetState), Panic(EGavdpStateMachineBadState));//this should always be true if no state change is in progress + + iChangeOfSelectedHeadsetSEP = EFalse; + if (aSettingsHaveChanged) + {//then see if we need to change SEP + //data type has changed so we need to choose a different SEP + //and configure it + //note if no match is found then we go with the exiting + //selected SEP + for (TUint i=0; i<iUsableSEPs.Count() ; i++) + { + TUsableSEP sep = iUsableSEPs[i]; + if (sep.iDataType == iA2dpCodecSettings.HeadsetCodecDataType()) + {//then we have a match so use this SEP + if (sep.iSEID != iHeadsetSEID) + {//then this is a different SEP to the one we were already using + iHeadsetSEID = sep.iSEID; + iLocalSEPConfigured = EFalse; + iChangeOfSelectedHeadsetSEP = ETrue;//need to config transport capabilites again if change of SEP + //need to change the symbian device SEP as well + TBool symbianDeviceSupportsNewSEPDataType(EFalse); + for(TUint i = 0; i< iSymbianDeviceSEPs.Count();i++) + { + if (iSymbianDeviceSEPs[i].iDataType == sep.iDataType) + { + iSymbianDeviceSEID = iSymbianDeviceSEPs[i].iSEID; + symbianDeviceSupportsNewSEPDataType = ETrue; + break; + } + } + __ASSERT_DEBUG(symbianDeviceSupportsNewSEPDataType, Panic(EGavdpStateMachineDataTypeMismatch)); + + //the if condition below is totaly unnecesaary since we have already + //ASSERTed symbianDeviceSupportsNewSEPDataType, however the ARM 5 compiler + //does not seem to recognize code in ASSERT statements so the if statement + //supresses an ARM v5 compiler warning + if (symbianDeviceSupportsNewSEPDataType) + { + iBearerSocket.Close(); //if we change headset SEP then the old bearer is no longer valid + } + } + } + } + } + + + + //if a reconfigure is required then we need to reset the current state + //to ESEPSelected and reconfigure + iInitialState = iCurrentState; //incase we need to wind back if state change fails + + if ((aSettingsHaveChanged)||(iChangeOfSelectedHeadsetSEP)) + { + iTargetState = iCurrentState; //need to end up in the same state we started at + iCurrentState = TGAVDPState::ESEPSelected; //roll back the state machine to ESEPSelected + iNextState = iCurrentState; + NextState(); //make sure the NextState is EConfigured + } + else + {//go to RunL to make GAVDPStateChangeComplete callback + if (!IsActive()) + { + TRequestStatus* stat = &iStatus; + User::RequestComplete(stat, KErrNone); + SetActive(); + } + } + return KErrNone; + } + + +/** +Advance to Next state +*/ +void CGAVDPStateMachine::NextState() + { + iStateChangeInProgress = ETrue; + TRequestStatus* stat = &iStatus; + ++iNextState; + if (!IsActive()) + { + User::RequestComplete(stat, KErrNone); + SetActive(); + } + } + + +/** +Internal function to complete the state change +*/ +void CGAVDPStateMachine::StateChangeComplete() + { + iCurrentState = iNextState; + if (iCurrentState == iTargetState) + { + iStateChangeInProgress = EFalse; + iGAVDPStateChangeObserver.GAVDPStateChangeComplete(iInitialState, KErrNone); + } + else + {//we've completed the internal state change but have not reached the target state + NextState(); + } + } + + +/** +Function to cancel a state change initialited by the ChangeState function +This function is called when the change state is explicitly cancelled by the client +*/ +void CGAVDPStateMachine::CancelChangeState() + { + //if we call this externally then the reason is KErrCancel + //as the state change has been explicitely cancelled + CancelChangeState(KErrCancel); + } + + +/** +Internal function to cancel a state change initiated by the ChangeState function +Usually called when an error occurs that would prevent the state change +from completing +*/ +void CGAVDPStateMachine::CancelChangeState(TInt aReason) + { + if (iStateChangeInProgress) + {//unwind the request as far as possible + switch (iInitialState.State()) + { + case TGAVDPState::EGAVDPIdle: + Reset(); + break; + case TGAVDPState::EGAVDPOpen: + iGavdp.Cancel(); + iCurrentState = TGAVDPState::EGAVDPOpen; + iNextState = TGAVDPState::EGAVDPOpen; + iTargetState = TGAVDPState::EGAVDPOpen; + break; + case TGAVDPState::EGAVDPStreaming: + iGavdp.Cancel(); + iNextState = TGAVDPState::EGAVDPStreaming; + iTargetState = TGAVDPState::EGAVDPStreaming; + break; + default: + Panic(EGavdpStateMachineBadState); + break; + } + } + if (IsActive()) + { + Cancel(); + } + iStateChangeInProgress = EFalse; + iGAVDPStateChangeObserver.GAVDPStateChangeComplete(iInitialState, aReason); + } + + +/** +Internal function to initiate the connection to the headset +*/ +void CGAVDPStateMachine::ConnectToGAVDP() + { + iGavdp.Connect(iBTDevAddr); + } + + +/** +MGavdpUser callback to indicate the headset is connected + +@see MGavdpUser +*/ +void CGAVDPStateMachine::GAVDP_ConnectConfirm(const TBTDevAddr& aDevice) + { + if (!iStateChangeInProgress) + {//connect initiated by remote headset so we'll accept the address + iBTDevAddr = aDevice; + iCurrentState == TGAVDPState::EConnectedToGavdp; + } + else if (iBTDevAddr == aDevice) + {//if a state change is in progress then we have already set the bt + //address so the above should be true + //if its not we'll treat the connect as a spurious connect and ignore + if (iCurrentState.State() == TGAVDPState::EGAVDPIdle) + {//if the above isn't true then must be a spurious connect from the headset + //now connected to the device in question + StateChangeComplete(); + } + } + } + + +/** +MGavdpUser callback to indicate the headset has suspended streaming +Makes a GAVDPStateMachineStreamSuspendedByRemoteHeadset MGAVDPStateChangeObserver +callback on the CA2dpBTHeadsetAudioInterface + +@see MGavdpUser +*/ +TInt CGAVDPStateMachine::GAVDP_SuspendIndication(TSEID aSEID) + { + TInt err = KErrNone; + //first check the callback is for the symbian device SEP we are using to + //send data to the a2dp headset - incase the Symbian device has more than one SEP + if (aSEID == iSymbianDeviceSEID) + { + __ASSERT_DEBUG((iCurrentState.State() == TGAVDPState::EGAVDPStreaming), Panic(EGavdpStateMachineUnexpectedCallback)); + + //need to stop the RTP streamer from sending more packets to the headset + iGAVDPStateChangeObserver.GAVDPStateMachineStreamSuspendedByRemoteHeadset(); + + iCurrentState = TGAVDPState::EGAVDPSuspended; + } + else + { + err = ConvertToSymbianError::AvdtpError(EAvdtpBadACPSEID); + } + return err; + } + + +/** +MGavdpUser callback to indicate the headset wished to configure the SymbianDevice + +@see MGavdpUser +*/ +void CGAVDPStateMachine::GAVDP_ConfigurationStartIndication(TSEID aLocalSEID, TSEID aRemoteSEID) + { + //first check the callback is for the symbian device SEP we are using to + //send data to the a2dp headset - incase the Symbian device has more than one SEP + if (aLocalSEID == iSymbianDeviceSEID) + { + __ASSERT_DEBUG((iCurrentState.State() != TGAVDPState::EGAVDPStreaming), Panic(EGavdpStateMachineUnexpectedCallback)); + __ASSERT_DEBUG(iConfigurationByRemoteHeadsetState.State() == TConfigurationByRemoteHeadsetState::ENotBeingConfiguredByRemoteHeadset, Panic(EGavdpStateMachineBadConfigByRemoteHeadsetState)); + + //cancel any current state changes - config is being driven by headset + Cancel(); + iGavdp.Cancel(); + //no timeouts are required as if the headset goes down ie does not + //result in the expected GAVDP_ConfigurationIndication callbacks a GAVDP_Error + //callback occurs. + + iConfigurationByRemoteHeadsetState = TConfigurationByRemoteHeadsetState::ERemoteHeadsetConfigurationStart; + + //we just store the remote SEP for now + iConfigurationByRemoteHeadsetState.SetSEPRequestedByRemoteHeadset(aRemoteSEID); + } + // else we can't return an error if the aLocalSEID is not for the SymbianOS device + } + + +/** +MGavdpUser callback to request the SymbianOS device to take the configuration passed in aCapability +Only SBC reconfigurations by the remote headset are supported, sample rate changes +are not supported, changes in the number of channels are not suported +if the capability is TSBCCodecCapabilities then the function checks that the values +requested can be accepted. + +@see MGavdpUser +*/ +TInt CGAVDPStateMachine::GAVDP_ConfigurationIndication(TAvdtpServiceCapability* aCapability) + { + //the underlying GAVDP/AVDTP code should ensure that this + //callback from GAVDP never occurs once the headset has been configured + __ASSERT_DEBUG((iCurrentState.State() != TGAVDPState::EGAVDPStreaming), Panic(EGavdpStateMachineUnexpectedCallback)); + __ASSERT_DEBUG(iConfigurationByRemoteHeadsetState.State() == TConfigurationByRemoteHeadsetState::ERemoteHeadsetConfigurationStart, Panic(EGavdpStateMachineBadConfigByRemoteHeadsetState)); + + TInt err = KErrNone; + + switch(aCapability->Category()) + { + case EServiceCategoryMediaTransport: + { + TAvdtpMediaTransportCapabilities* mediaTransportCaps = static_cast<TAvdtpMediaTransportCapabilities*>(aCapability); + iConfigurationByRemoteHeadsetState.SetTransportCapsRequestedByRemoteHeadset(mediaTransportCaps); + } + break; + case EServiceCategoryMediaCodec: + { + TAvdtpMediaCodecCapabilities* codecCaps = static_cast<TAvdtpMediaCodecCapabilities*>(aCapability); + if (codecCaps->MediaType() != EAvdtpMediaTypeAudio) + { + err = ConvertToSymbianError::A2dpError(EA2dpNotSupportedCodec); + } + //we're only going to allow a reconfiguration for SBC + if ((codecCaps->MediaCodecType() == EAudioCodecSBC) && (iA2dpCodecSettings.HeadsetCodecDataType() == KMMFFourCCCodeSBC)) + { + TSBCCodecCapabilities* sbcCodecCapabilities = static_cast<TSBCCodecCapabilities*>(codecCaps); + TSBCSamplingFrequencyBitmask samplingFreqBitMask = sbcCodecCapabilities->SamplingFrequencies(); + + //if the headset has already been configured then we don't allow a change + //of sampling frequency. This is because once the DevSound has been configured + //with a sampling frequency after the DevSound is initialized and hence + //the a2dpheadsetif is initialized, it is no longer possible to change the sample frequency + TBool headsetAlreadyConfigured(EFalse); + if ((iCurrentState.State() == TGAVDPState::EGAVDPOpen) || + (iCurrentState.State() == TGAVDPState::EGAVDPSuspended)) + { + headsetAlreadyConfigured = ETrue; + } + //only one bit of the sampling frequency mask should be set + //note we're using switch statements rather than if then else + //it makes it easier to pick up the case where we have an invalid + //value or more than one value has been set in the bit mask + switch (samplingFreqBitMask) + { + case E16kHz: + if ((headsetAlreadyConfigured) && (iA2dpCodecSettings.SampleRate() != 16000)) + { + err = ConvertToSymbianError::A2dpError(EA2dpNotSupportedSamplingFrequency); + } + break; + case E32kHz: + if ((headsetAlreadyConfigured) && (iA2dpCodecSettings.SampleRate() != 32000)) + { + err = ConvertToSymbianError::A2dpError(EA2dpNotSupportedSamplingFrequency); + } + break; + case E44100Hz: + if ((headsetAlreadyConfigured) && (iA2dpCodecSettings.SampleRate() != 44100)) + { + err = ConvertToSymbianError::A2dpError(EA2dpNotSupportedSamplingFrequency); + } + break; + case E48kHz: + if ((headsetAlreadyConfigured) && (iA2dpCodecSettings.SampleRate() != 48000)) + { + err = ConvertToSymbianError::A2dpError(EA2dpNotSupportedSamplingFrequency); + } + break; + default: + err = ConvertToSymbianError::A2dpError(EA2dpInvalidSamplingFrequency); + break; + } + TSBCChannelModeBitmask channelModeBitMask = sbcCodecCapabilities->ChannelModes(); + + //if the headset has already been configured then we don't allow a change + //of channels. This is because once the DevSound has been configured + //with a sampling frequency after the DevSound is initialized and hence + //the a2dpheadsetif is initialized, it is no longer possible to change the number of channels + switch (channelModeBitMask) + { + case EMono: + if ((headsetAlreadyConfigured) && (iA2dpCodecSettings.Channels() != EMMFMono)) + { + err = ConvertToSymbianError::A2dpError(EA2dpNotSupportedChannelMode); + } + break; + case EDualChannel: //we don't support dual channel + err = ConvertToSymbianError::A2dpError(EA2dpNotSupportedChannelMode); + break; + case EStereo: + if ((headsetAlreadyConfigured) && (iA2dpCodecSettings.Channels() != EMMFStereo)) + { + err = ConvertToSymbianError::A2dpError(EA2dpNotSupportedChannelMode); + } + break; + case EJointStereo: + if ((headsetAlreadyConfigured) && (iA2dpCodecSettings.Channels() != EMMFStereo)) + { + err = ConvertToSymbianError::A2dpError(EA2dpNotSupportedChannelMode); + } + break; + default: + err = ConvertToSymbianError::A2dpError(EA2dpInvalidChannelMode); + break; + } + + TSBCBlockLengthBitmask blockLengthBitMask = sbcCodecCapabilities->BlockLengths(); + + switch (blockLengthBitMask) + { + case EBlockLenFour: + break; + case EBlockLenEight: + break; + case EBlockLenTwelve: + break; + case EBlockLenSixteen: + break; + default: + err = ConvertToSymbianError::A2dpError(EA2dpInvalidBlockLength); + break; + } + + TSBCSubbandsBitmask subbandsBitMask = sbcCodecCapabilities->Subbands(); + + TUint subbands = 0; + switch (subbandsBitMask) + { + case EFourSubbands: + subbands = 4; + break; + case EEightSubbands: + subbands = 8; + break; + default: + err = ConvertToSymbianError::A2dpError(EA2dpInvalidSubbands); + break; + } + + TSBCAllocationMethodBitmask allocationMethodBitMask = sbcCodecCapabilities->AllocationMethods(); + + switch (allocationMethodBitMask) + { + case ELoudness: + break; + case ESNR: + break; + default: + err = ConvertToSymbianError::A2dpError(EA2dpInvalidAllocationMethod); + break; + } + + TInt bitPoolValue = sbcCodecCapabilities->MinBitpoolValue(); + + if (bitPoolValue < KMinBitPoolValue) + { + err = ConvertToSymbianError::A2dpError(EA2dpInvalidMinimumBitPoolValue); + } + + bitPoolValue = sbcCodecCapabilities->MaxBitpoolValue(); + + //The bitpool value must be in the range of 2-250 and must not exceed + // 16*numberOfSubbands*channels + if ((bitPoolValue > KMaxBitPoolValue)||(bitPoolValue > (16*subbands*iA2dpCodecSettings.Channels()))) + { + err = ConvertToSymbianError::A2dpError(EA2dpInvalidMaximumBitPoolValue); + } + if (!err) + {//note ownership of codecCaps is transferred + iConfigurationByRemoteHeadsetState.SetCodecConfigRequestedByRemoteHeadset(codecCaps); + } + }//if (codecCaps->MediaCodecType() == EAudioCodecSBC) && (iA2dpCodecSettings.HeadsetCodecDataType() == KMMFFourCCodeSBC)) + else + {//only support SBC for now + err = ConvertToSymbianError::A2dpError(EA2dpNotSupportedCodec); + } + } + break; //case EServiceCategoryMediaCodec: + default: // switch(aCapability->Category()) + err = KErrNotSupported; + break; + } + + if (err) + { + iConfigurationByRemoteHeadsetState = TConfigurationByRemoteHeadsetState::ERemoteHeadsetConfigurationFailed; + if ((iCurrentState.State() < TGAVDPState::EConfigured) && (iCurrentState.State() != TGAVDPState::EGAVDPIdle)) + {//we only abort if the remote headset has not been configured + //need to abort using SEID of remote headset - this resets config at remote headset + Cancel(); //probably not necessary - but just in case + iGavdp.AbortStream(iConfigurationByRemoteHeadsetState.SEPRequestedByRemoteHeadset()); + } + //else + //config is a reconfig in suspend streams + //wait on a Start_Indication if the headset is ok with the existing config + //or an Abort_Indication if the headset is not + } + + return err; + } + + +/* +MGavdpUser callback to indicate to the SymbianOS device that all the GAVDP_ConfigurationIndications +have been sent. +This callback should only occur if there have been no errored GAVDP_ConfigurationIndications + +@see MGavdpUser +*/ +TInt CGAVDPStateMachine::GAVDP_ConfigurationEndIndication() + { + //the underlying GAVDP/AVDTP code should ensure that this + //callback from GAVDP never occurs once the headset has been configured + __ASSERT_DEBUG((iCurrentState.State() != TGAVDPState::EGAVDPStreaming), Panic(EGavdpStateMachineUnexpectedCallback)); + __ASSERT_DEBUG(iConfigurationByRemoteHeadsetState.State() == TConfigurationByRemoteHeadsetState::ERemoteHeadsetConfigurationStart, Panic(EGavdpStateMachineBadConfigByRemoteHeadsetState)); + + TInt err = KErrNone; + + TAvdtpMediaCodecCapabilities* configRequestedByRemoteHeadset = iConfigurationByRemoteHeadsetState.CodecConfigRequestedByRemoteHeadset(); + if (configRequestedByRemoteHeadset) + { + __ASSERT_DEBUG(iConfigurationByRemoteHeadsetState.SEPRequestedByRemoteHeadset().IsValid(), Panic(EGavdpStateMachineBadConfigByRemoteHeadsetState)); + + //the headset settings have been accepted so use the headset settings + //the only usable SEP shall be the once specified by the headset + //for now this will always be SBC + TUsableSEP SEP; + SEP.iSEID = iConfigurationByRemoteHeadsetState.SEPRequestedByRemoteHeadset(); + TFourCC dataTypeSupport; + switch (configRequestedByRemoteHeadset->MediaCodecType()) + { + case EAudioCodecSBC: + dataTypeSupport.Set(KMMFFourCCCodeSBC); + break; + case EAudioCodecMPEG12Audio: + dataTypeSupport.Set(KMMFFourCCCodeMP3); + break; + case EAudioCodecMPEG24AAC: + dataTypeSupport.Set(KMMFFourCCCodeAAC); + break; + case EAudioCodecATRAC: + dataTypeSupport.Set(KMMFFourCCCodeATRAC3); + break; + default://should never get here + Panic(EGavdpStateMachineBadConfigByRemoteHeadsetState); + break; + } + SEP.iDataType.Set(dataTypeSupport); + iUsableSEPs.Reset(); + err = iUsableSEPs.Append(SEP); + + //the only service capabilities are codec caps provided by the headset and transport + if (!(iA2dpCodecSettings.UpdateRemoteCodecConfiguration(*configRequestedByRemoteHeadset))) + { + err = KErrNoMemory; + } + if (!err) + { + iSEPCapabilities.ResetAndDestroy(); + err = iSEPCapabilities.Append(iConfigurationByRemoteHeadsetState.TransportCapsRequestedByRemoteHeadset()); + if (!err) + { + err = iSEPCapabilities.Append(iConfigurationByRemoteHeadsetState.CodecConfigRequestedByRemoteHeadset()); + } + } + }// if (codecCaps) + + if (!err) + { + if ((iStateChangeInProgress) && (iInitialState.State() == TGAVDPState::EGAVDPIdle)) + {//the headset configured us during an a2dpheadsetif Initialize() + //we're now configured so now wait for GAVDP_Bearer ready + iCurrentState == TGAVDPState::EConfigured; + } + else if (iCurrentState == TGAVDPState::EGAVDPSuspended) + { + //we have reconfigured so the SBC codec needs reconfiguring + err = iGAVDPStateChangeObserver.GAVDPStateMachineReconfigureByRemoteHeadset(); + } + } + if (err) + { + if ((iCurrentState.State() < TGAVDPState::EConfigured) && (iCurrentState.State() != TGAVDPState::EGAVDPIdle)) + {//we only abort if the remote headset has not been configured + //need to abort using SEID of remote headset - this resets config at remote headset + Cancel(); //probably not necessary - but just in case + iGavdp.AbortStream(iConfigurationByRemoteHeadsetState.SEPRequestedByRemoteHeadset()); + } + } + //else + //config is a reconfig in suspend streams + //wait on a Start_Indication if the headset is ok with the existing config + //or an Abort_Indication if the headset is not + iConfigurationByRemoteHeadsetState = TConfigurationByRemoteHeadsetState::ERemoteHeadsetConfigurationEnd; + return err; + } + + +/** +MGavdpUser callback to indicate to the SymbianOS device that it has started streaming +There are two circumstances we can get a start indication +1) The headset can request to start the streams itself once we are in the open state +2) The headset can request a restart after suspending the streams eg to perform a reconfiguration + +@see MGavdpUser +*/ +TInt CGAVDPStateMachine::GAVDP_StartIndication(TSEID aSEID) + { + __ASSERT_DEBUG((iCurrentState.State() == TGAVDPState::EGAVDPOpen) || + (iCurrentState.State() == TGAVDPState::EGAVDPSuspended), Panic(EGavdpStateMachineUnexpectedCallback)); + __ASSERT_DEBUG((iConfigurationByRemoteHeadsetState.State() != TConfigurationByRemoteHeadsetState::ERemoteHeadsetConfigurationStart),Panic(EGavdpStateMachineBadConfigByRemoteHeadsetState)); + //first check the callback is for the symbian device SEP we are using to + //send data to the a2dp headset - incase the Symbian device has more than one SEP + if (aSEID == iSymbianDeviceSEID) + { + if (iCurrentState.State() == TGAVDPState::EGAVDPOpen) + { + if (iStateChangeInProgress) + { + if (iTargetState.State() == TGAVDPState::EGAVDPStreaming) + { + //a EGAVDPOpen->Streaming transistion + //which has been completed for us by the headset + Cancel(); + iGavdp.Cancel(); + iCurrentState = TGAVDPState::EGAVDPStreaming; + StateChangeComplete(); + } + //else only other state change would be open->idle + //this is unlikely/impossible if we get a GAVDP_StartIndication + //do nothing + } + else + { + //note if no state change is in progress then the headset itself + //has put itself in the streaming state + //normally we put the headset in the streaming state + //when we call OpenDevice() on the a2dpaudioif + //but if the headset does this then it is no longer possible + //for the client to change the audio settings + iCurrentState = TGAVDPState::EGAVDPStreaming; + } + }//(iCurrentState.State() == TGAVDPState::EGAVDPOpen) + else if (iCurrentState.State() == TGAVDPState::EGAVDPSuspended) + {//most probably restarting after a reconfigure + iCurrentState = TGAVDPState::EGAVDPStreaming; + iGAVDPStateChangeObserver.GAVDPStateMachineStreamResumedByRemoteHeadset(); + } + }//if (aSEID == iSymbianDeviceSEID) + return KErrNone; + } + + +/** +MGavdpUser callback to indicate to the SymbianOS device that the request to abort the headset +which means it is not configured has completed +Can request an abort stream if headset configuration fails + +@see MGavdpUser +*/ +void CGAVDPStateMachine::GAVDP_AbortStreamConfirm() + { + //if abort stream was caused by aborting the headset in response + //to a headset configuration in GAVDP_ConfigurationIndication + //then restart state machine from EConnectedToGavdp + if (iConfigurationByRemoteHeadsetState.State() == TConfigurationByRemoteHeadsetState::ERemoteHeadsetConfigurationFailed) + {//then the abort was caused an aborted configuration from the headset + __ASSERT_DEBUG((iCurrentState.State() < TGAVDPState::EConfigured), Panic(EGavdpStateMachineUnexpectedCallback)); + + //since we only aborted if the headset was not yet configured + //there must be a state change in progress and since we cancelled + //the GAVDPStateMachine should not be active + __ASSERT_DEBUG((iStateChangeInProgress) && (!IsActive()), Panic(EGavdpStateMachineBadConfigByRemoteHeadsetState)); + + iConfigurationByRemoteHeadsetState.Reset(); + //just to make extra sure we'll rediscover the SEPs + iPotentialSEPs.Reset(); + iUsableSEPs.Reset(); + iSEPCapabilities.ResetAndDestroy(); + iCurrentState = TGAVDPState::EConnectedToGavdp; + iNextState = TGAVDPState::EConnectedToGavdp; + iConfigurationByRemoteHeadsetState.Reset(); + iA2dpCodecSettings.Reset(); + iHeadsetSEID.Reset(); + iBearerSocket.Close(); + iLocalSEPConfigured = EFalse; + iChangeOfSelectedHeadsetSEP = EFalse; + iSignallingTransactionTimeout->Cancel(); + NextState(); + } + iConfigurationByRemoteHeadsetState.Reset(); + } + + +/** +MGavdpUser callback to indicate to the SymbianOS device that the headset has aborted +ie it can no longer be regarded as configured +This makes a GAVDPStateMachineEvent MGAVDPStateChangeObserver callback on the CA2dpBTHeadsetAudioInterface +(which inturn will cause a Reset on the GAVDP state machine) + +@see MGavdpUser +*/ +void CGAVDPStateMachine::GAVDP_AbortIndication(TSEID aSEID) + { + //first check the callback is for the symbian device SEP we are using to + //send data to the a2dp headset - incase the Symbian device has more than one SEP + if (aSEID == iSymbianDeviceSEID) + { + if (iStateChangeInProgress) + { + CancelChangeState(KErrAbort); + } + else + { + iGAVDPStateChangeObserver.GAVDPStateMachineEvent(KErrAbort); + } + } + //else not much we can do it aSEID isn't the Symbian device ID + iConfigurationByRemoteHeadsetState.Reset(); + } + + +/** +Internal function to initiate the discovery of the headset SEPs +Note that the SEP discovery does not have a built in timeout +so the GAVDP_SEPDiscovered callback must occur withing the GAVDP callback timeout 30s +or the state change will be cancelled with KErrTimedOut +*/ +void CGAVDPStateMachine::DiscoverRemoteSEPs() + { + iPotentialSEPs.Reset(); + iGavdp.DiscoverRemoteSEPs(); + iSignallingTransactionTimeout->StartTimer(TTimeIntervalMicroSeconds32(KGAVDPCallbackTimeout)); + } + + +/** +MGavdpUser callback in response to DiscoverRemoteSEPs + +@see MGavdpUser +*/ +void CGAVDPStateMachine::GAVDP_SEPDiscovered(const TAvdtpSEPInfo& aSEP) + { + //check whether the SEP is suitable for connection + if ((!aSEP.InUse()) && (aSEP.MediaType() == EAvdtpMediaTypeAudio) && (aSEP.IsSink())) + {//the SEP is a potentialy suitable for connection + //so add to list of potential SEPs + TInt err = iPotentialSEPs.Append(aSEP.SEID()); + if (err) + { + CancelChangeState(err); + } + } + //else SEP is not suitable so ignore it + } + + +/** +MGavdpUser callback + +@see MGavdpUser +*/ +void CGAVDPStateMachine::GAVDP_SEPDiscoveryComplete() + { + iSignallingTransactionTimeout->Cancel(); + if (iPotentialSEPs.Count()) + {//we have at least one potential SEP + StateChangeComplete(); + } + else + {//no potential SEPs were found so cannot proceed any further + CancelChangeState(KErrCouldNotConnect); + } + } + + +/** +Internal function to get the capability of an individual SEP +*/ +void CGAVDPStateMachine::GetRemoteSEPCapability() + { + TAvdtpServiceCategories serviceCategories; + serviceCategories.SetCapability(EServiceCategoryMediaTransport); + serviceCategories.SetCapability(EServiceCategoryMediaCodec); + TSEID SEPtoGetCapabilityOf = iPotentialSEPs[iSEPIterator]; + iGavdp.GetRemoteSEPCapabilities(SEPtoGetCapabilityOf, serviceCategories); + iSignallingTransactionTimeout->StartTimer(TTimeIntervalMicroSeconds32(KGAVDPCallbackTimeout)); + } + + +/** +Internal function to iterate through the list of potential SEPs and asking +for the capabilites of each one. +*/ +void CGAVDPStateMachine::GetRemoteSEPCapabilities() + { + iSEPCapabilities.ResetAndDestroy(); //clear what's already there + iUsableSEPs.Reset(); + iSEPIterator = 0; + iSEPCapabilityEntry = 0; + //go and get the capability of the first SEP in the list of potential SEPs + //the GAVDP_SEPCapabilityComplete() callback will cause the next potential + //SEP capability to be obtained. + GetRemoteSEPCapability(); + } + + +/** +MGavdpUser callback in response to GetRemoteSEPCapabilities + +@see MGavdpUser +*/ +void CGAVDPStateMachine::GAVDP_SEPCapability(TAvdtpServiceCapability* aCapability) + { + // we own cap, stash it in the iSEPCapabilities array for owning and later use + TInt err = iSEPCapabilities.Append(aCapability); + if (err) + { + CancelChangeState(err); + } + } + + +/** +MGavdpUser callback in response to GetRemoteSEPCapabilities when all the +GAVDP_SEPCapability callbacks have been made for an individual SEP + +The function iterates through the SEP capabilites looking for transport and media codec support +and if so the SEP is added to the list of usableSEPs. + +@see MGavdpUser +*/ +void CGAVDPStateMachine::GAVDP_SEPCapabilityComplete() + { + TInt err = KErrNone; + iSignallingTransactionTimeout->Cancel(); + //iterate through the capabilities + TAvdtpServiceCapability* avdtpServiceCapability; + TBool mediaTransport(EFalse); + TBool audioSupport(EFalse); + TFourCC dataTypeSupport; //note we are assuming here that each SEP only has one codec capability + TUint i = 0; + //iterate through the capabilities + //looking for media transport and codec support + for (;iSEPCapabilityEntry < iSEPCapabilities.Count() ; iSEPCapabilityEntry++) + { + avdtpServiceCapability = iSEPCapabilities[iSEPCapabilityEntry]; + switch (avdtpServiceCapability->Category()) + { + case EServiceCategoryMediaTransport: + mediaTransport = ETrue; + break; + case EServiceCategoryMediaCodec: + { + TAvdtpMediaCodecCapabilities* codecCaps = static_cast<TAvdtpMediaCodecCapabilities*>(avdtpServiceCapability); + if (codecCaps->MediaType() == EAvdtpMediaTypeAudio) + { + audioSupport = ETrue; + } + switch (codecCaps->MediaCodecType()) + { + case EAudioCodecSBC: + dataTypeSupport.Set(KMMFFourCCCodeSBC); + break; + case EAudioCodecMPEG12Audio: + dataTypeSupport.Set(KMMFFourCCCodeMP3); + break; + case EAudioCodecMPEG24AAC: + dataTypeSupport.Set(KMMFFourCCCodeAAC); + break; + case EAudioCodecATRAC: + dataTypeSupport.Set(KMMFFourCCCodeATRAC3); + break; + default: + //the datatype is an unsupported datatype + //so set to NULL to show we can't use it + dataTypeSupport.Set(KMMFFourCCCodeNULL); + break; + } + } + }//switch (avdtpServiceCapability->Category()) + }// for (TUint i=0; i++; i<iSEPCapabilities.Count()) + //check if the capabilities indicate that the SEP is usable + //for audio transport + if ((mediaTransport) && (audioSupport) && + (dataTypeSupport != KMMFFourCCCodeNULL)) + {//then we can use this SEP to stream audio to the headset + // however we ie the Symbian device must + //also be able to support the data type + //before we can add it to the list of usable SEPs + TBool symbianDeviceSupportsDataType(EFalse); + for(TUint i = 0; i< iSymbianDeviceSEPs.Count();i++) + { + if (iSymbianDeviceSEPs[i].iDataType == dataTypeSupport) + { + symbianDeviceSupportsDataType = ETrue; + break; + } + } + if (symbianDeviceSupportsDataType) + { + TUsableSEP usableSEP; + usableSEP.iSEID = iPotentialSEPs[iSEPIterator]; + usableSEP.iDataType.Set(dataTypeSupport); + err = iUsableSEPs.Append(usableSEP); + if (err) + { + CancelChangeState(err); + } + } + } + if (!err) + { + //check if we have finished going through our list of potential SEPs + //that we need to find the capabilities of + iSEPIterator++; + if (iSEPIterator >= iPotentialSEPs.Count()) + {//then we have finished getting all the capabilities + //so lets choose a SEP by iterating through the + //usable SEPs and stopping at the first one that supports the + //required data type + TUsableSEP SEP; + TBool SEPFound(EFalse); + for (i=0; i<iUsableSEPs.Count(); i++) + { + SEP = iUsableSEPs[i]; + if (SEP.iDataType == iA2dpCodecSettings.HeadsetCodecDataType()) + {//one of the usable SEPs supports the requested data type + SEPFound = ETrue; + iHeadsetSEID = SEP.iSEID; //we'll use this SEID for now + break; + } + } + iSEPIterator = 0; + iSEPCapabilityEntry = 0; + if (SEPFound) + { + //we've selected a SEP + StateChangeComplete(); //so move on to the next state + } + else + { + //then non of the SEPs have SBC codec capabilites + //or no usable SEPs have been found or SEP may be in use + //since SBC support is mandatory we do not have a suitable SEP + CancelChangeState(KErrCouldNotConnect); + } + }// if (iPotentialSEPs.Count() >= iSEPIterator) + else + {//get the capability of the next SEP + GetRemoteSEPCapability(); + } + } + } + + +/** +Function used to return a TAvdtpMediaCodecCapabilities structure +that can be used to configure the SEP at the remote end ie on the headset +The capabilities are used to determine the configuration +need to return by pointer rather than by ref as TAvdtpMediaCodecCapabilities is abstract +*/ +TAvdtpMediaCodecCapabilities* CGAVDPStateMachine::RemoteCodecConfiguration() + { + TAvdtpMediaCodecCapabilities* codecCaps = NULL; + TAvdtpMediaCodecCapabilities* codecConfiguration = NULL; + + //first get the capabilities of the codec + TInt error = CodecCaps(codecCaps); + if (!error) + {//and use the capabilities to get a valid remote codec configuration + codecConfiguration = iA2dpCodecSettings.UpdateRemoteCodecConfiguration(*codecCaps); + } + + return codecConfiguration; + } + + +/** +Internal function to configure the local SEP +*/ +TInt CGAVDPStateMachine::ConfigureLocalSEP() + { + TInt err = iGavdp.BeginConfiguringLocalSEP(iSymbianDeviceSEID); + if (err == KErrNotFound) + { + //could be a problem with the local SEP no longer being registered + //so register and try again + err = RegisterLocalSEP(); + if (!err) + { + err = iGavdp.BeginConfiguringLocalSEP(iSymbianDeviceSEID); + } + } + if (!err) + {//configure local SEP - we only advertise one local SEP with SBC support + //in future when capability structures are defined for mp3,AAC,ATRAC3 + //then these can also be advertised as local SEPs + TAvdtpMediaTransportCapabilities media; + err = iGavdp.AddSEPCapability(media); + + if (!err) + { + CA2dpLocalCodecCapabilities* localCodecCapabilities = NULL; + TRAP(err, localCodecCapabilities = CA2dpLocalCodecCapabilities::NewL()) + if (!err) + { + TAvdtpMediaCodecCapabilities* localSEPCapability = localCodecCapabilities->LocalCodecCapabilities(iA2dpCodecSettings.HeadsetCodecDataType()); + if (localSEPCapability) + { + err = iGavdp.AddSEPCapability(*localSEPCapability); + } + else + { + err = KErrNoMemory; + } + delete localCodecCapabilities; + localCodecCapabilities = NULL; + } + } + } + return err; + } + + +/** +Internal function to configure the remote SEP ie the headset + +If the SEP has never been configured before then the transport caps are added +If we are doing a reconfigure of the remote SEP then just the codec caps are added +*/ +TInt CGAVDPStateMachine::ConfigureRemoteSEP() + { + TInt err = iGavdp.BeginConfiguringRemoteSEP(iHeadsetSEID, iSymbianDeviceSEID); + if (!err) + { + if ((iInitialState.State() < TGAVDPState::EConfigured)||(iChangeOfSelectedHeadsetSEP)) + {//then this SEP has never been configured before + //in which case we need to configure it with transport capabilities + //note that if the SEP has been configured before ie the above condition + //is false then we are performing a reconfiguration and are not allowed + //to reconfigure the media transport capabilities + TAvdtpMediaTransportCapabilities avdtpMediaTransportCapabilities; + err = iGavdp.AddSEPCapability(avdtpMediaTransportCapabilities); + iChangeOfSelectedHeadsetSEP = EFalse;//reset + } + if (!err) + { + TAvdtpMediaCodecCapabilities* codecConfiguration = RemoteCodecConfiguration(); + //note we are setting a configuration here not a capability + if (codecConfiguration) + { + err = iGavdp.AddSEPCapability(*codecConfiguration); + } + else + {//we were not able to get a valid configuration + //so abort configuration + err = KErrAbort; + } + }// if (!err) + } + return err; + } + + +/** +Internal function to initiate SEP configuration of both the local and remote SEPs +*/ +void CGAVDPStateMachine::ConfigureSEP() + { + if (iHeadsetSEID.IsValid()) + { + TInt err; + if (!iLocalSEPConfigured) + {//the local SEP must be configured first before configuring the remote SEP + err = ConfigureLocalSEP(); + } + else + {//local SEP is already configured so configure remote SEP + err = ConfigureRemoteSEP(); + } + if (err) + { + CancelChangeState(err); + } + else + { + iGavdp.CommitSEPConfiguration(); + } + } + else + {//we've requested to configure a SEP before we have + //a valid SEP to configure + CancelChangeState(KErrNotReady); + } + } + + +/** +MGavdpUser callback to confirm that the SEP configuration has been acepted +This callback should occur for both the local SEP and the remote SEP +*/ +void CGAVDPStateMachine::GAVDP_ConfigurationConfirm() + { + TInt err = KErrNone; + if (!iLocalSEPConfigured) + {//the local SEP is configured first so this call back must be from + //a local SEP configuration + iLocalSEPConfigured = ETrue; + //now configure the remote SEP + err = ConfigureRemoteSEP(); + if (!err) + { + iGavdp.CommitSEPConfiguration(); + } + else + { + CancelChangeState(err); + } + } + else + { + //local and remote SEPs now configured + TInt err = iGavdp.Listen(); + //note that if there is an error above + // there is not much we can do so ignore it + StateChangeComplete(); + } + } + + +/** +Internal function to request a bearer RSocket in order to stream audio to the headset +*/ +void CGAVDPStateMachine::CreateBearerSocket() + { + //then we need to request for one from the remote SEP + //this reference code does not support reporting and recovery channels + + if (!iBearerSocket.SubSessionHandle()) + {//we don't already have a bearer socket, create one - note no reporting and recovery implementation + TInt err = iGavdp.CreateBearerSockets(iHeadsetSEID, EFalse, EFalse); + if (err) + { + CancelChangeState(err); + } + } + else + { + //we already have a bearer socket so no need to create a new one + //just complete the state change + StateChangeComplete(); + } + } + + +/** +MGavdpUser callback to supply the RSocket used to stream audio to the headset + +This callback can occur either via a direct request from the headset or in response +to a CreateBearerSocket + +@see MGavdpUser +*/ +void CGAVDPStateMachine::GAVDP_BearerReady(RSocket aSocket, const TAvdtpSockAddr& /*aAddress*/) + { + //This call back can occur without a prior call to CreateBearerSocket + iBearerSocket = aSocket; + if ((iCurrentState == TGAVDPState::EConfigured)&&(iStateChangeInProgress)) + {//we've completed the state + StateChangeComplete(); + } + // else this came from the headset without a call to RGavdp::CreateBearerSockets + } + + +/** +Function to return an array of usable SEPs on the headset. +By 'usable' we mean the SEP supports audio, has media transport caps, is not in use, +has a supported audio codec and the audio codec is supported by the symbianOS device +@return array of usable SEPs +*/ +RArray<TUsableSEP>& CGAVDPStateMachine::UsableSEPs() const + { + return const_cast<RArray<TUsableSEP>&>(iUsableSEPs); + } + + +/** +Function to return the bearer socket used to stream audio to the headset +@return socket +*/ +RSocket& CGAVDPStateMachine::BearerSocket() const + { + return const_cast<RSocket&>(iBearerSocket); + } + + +/** +Function to return the headset BT address +@return headset BT address +*/ +TBTDevAddr& CGAVDPStateMachine::BTAddress() const + { + return const_cast<TBTDevAddr&>(iBTDevAddr); + } + + +/** +Function to return the SEPCapability for the codec settings +used by CA2dpBTHeadsetAudioInterface to determine the audio settings +ie sample rate / stereo support. +aCodecCaps is set with the TAvdtpMediaCodecCapabilities of the codec in use +if no codec has been specified via the Reconfigure() function then +the codec defaults to SBC +returns KErrNotReady if no SEP capablities have been obtained from the SEP +return KErrNotSupported if the codec type is not known +aCodecCaps Note that ownership is not transferred to the calling class. + +@param aCodecCaps The pointer points to a codec capabilities structure + +@return SymbianOS error code +*/ +TInt CGAVDPStateMachine::CodecCaps(TAvdtpMediaCodecCapabilities*& aCodecCaps) const + { + TInt err = KErrNotReady; + TAvdtpServiceCapability* avdtpServiceCapability; + TFourCC dataType; + for (TUint i=0; i<iSEPCapabilities.Count(); i++) + { + avdtpServiceCapability = iSEPCapabilities[i]; + if (avdtpServiceCapability->Category() == EServiceCategoryMediaCodec) + { + TAvdtpMediaCodecCapabilities* codecCaps = static_cast<TAvdtpMediaCodecCapabilities*>(avdtpServiceCapability); + switch (codecCaps->MediaCodecType()) + { + case EAudioCodecSBC: + dataType.Set(KMMFFourCCCodeSBC); + break; + case EAudioCodecMPEG12Audio: + dataType.Set(KMMFFourCCCodeMP3); + break; + case EAudioCodecMPEG24AAC: + dataType.Set(KMMFFourCCCodeAAC); + break; + case EAudioCodecATRAC: + dataType.Set(KMMFFourCCCodeATRAC3); + break; + default: + err = KErrNotSupported; + break; + } + if (dataType == iA2dpCodecSettings.HeadsetCodecDataType()) + {//then we have the capabilities for the selected datatype + aCodecCaps = codecCaps; + err = KErrNone; + break; + } + } + } //for (TUint i=0; i<iSEPCapabilities.Count(); i++) + return err; + } + + +/** +MGavdpUser callback when headset releases stream + +@see MGavdpUser +*/ +void CGAVDPStateMachine::GAVDP_ReleaseIndication(TSEID aSEID) + { + if (aSEID == iSymbianDeviceSEID) + { + if (iStateChangeInProgress) + { + CancelChangeState(KErrDisconnected); + } + else + { + iGAVDPStateChangeObserver.GAVDPStateMachineEvent(KErrDisconnected); + } + TInt err = iGavdp.Listen();//can't do much if this errors + } + } + + +/** +MGavdpUser error callback + +@see MGavdpUser +*/ +void CGAVDPStateMachine::GAVDP_Error(TInt aError, const TDesC8& /*aErrorData*/) + { + TInt error = ConvertToStandardSymbianOSError(aError); + if (iStateChangeInProgress) + { + CancelChangeState(error); + } + else + { + //error must have occured while no state change was in progress + //this could be due to a problem on the headset eg no longer in range + //Make callback on a2dpBTHeadsetIf so it can reset back to the idle state + iGAVDPStateChangeObserver.GAVDPStateMachineEvent(error); + } + //start listening for connect event from headset + TInt err = iGavdp.Listen();//can't do much if this errors + } + + +/** +Internal function to put the headset in the streaming state +*/ +void CGAVDPStateMachine::StartStream() + { + //we're only going to start one transport stream + iGavdp.StartStream(iHeadsetSEID); + } + + +/** +MGavdpUser callback in response to StartStream + +@see MGavdpUser +*/ +void CGAVDPStateMachine::GAVDP_StartStreamsConfirm() + { + StateChangeComplete(); + } + + +/** +Internal function to tell the headset we are suspending the stream +*/ +void CGAVDPStateMachine::SuspendStream() + { + //we're only going to suspend one transport stream + iGavdp.SuspendStream(iHeadsetSEID); + } + + +/** +MGavdpUser callback in response to SuspendStream + +@see MGavdpUser +*/ +void CGAVDPStateMachine::GAVDP_SuspendStreamsConfirm() + { + StateChangeComplete(); + } + + +/** +RunsL for GAVDP state machine +*/ +void CGAVDPStateMachine::RunL() + { + if (iStateChangeInProgress) + { + switch (iCurrentState.State()) + { + case TGAVDPState::EGAVDPIdle: + ConnectToGAVDP(); + break; + case TGAVDPState::EConnectedToGavdp: + DiscoverRemoteSEPs(); + break; + case TGAVDPState::ESEPsDiscovered: + GetRemoteSEPCapabilities(); + break; + case TGAVDPState::ESEPSelected: + ConfigureSEP(); + break; + case TGAVDPState::EConfigured: + CreateBearerSocket(); + break; + case TGAVDPState::EGAVDPOpen: + StartStream(); + break; + case TGAVDPState::EGAVDPStreaming: + SuspendStream(); + break; + default: + Panic(EGavdpStateMachineBadState); + break; + } + } + else + { + //if RunL should get called when no state + //change is in progress eg during a reconfiguration + //then the current state should always be identical to the target state + __ASSERT_DEBUG((iCurrentState == iTargetState), Panic(EGavdpStateMachineBadState)); + StateChangeComplete(); + } + } + + +/** +Cancel +*/ +void CGAVDPStateMachine::DoCancel() + { + TRequestStatus* stat = &iStatus; + User::RequestComplete(stat, KErrCancel); + } + + + + +CGavdpTimeout* CGavdpTimeout::NewL(CGAVDPStateMachine& aGAVDPStateMachine) + { + CGavdpTimeout* self = new(ELeave)CGavdpTimeout(); + CleanupStack::PushL(self); + self->ConstructL(aGAVDPStateMachine); + CleanupStack::Pop(); + return self; + } + + +void CGavdpTimeout::ConstructL(CGAVDPStateMachine& aGAVDPStateMachine) + { + CTimer::ConstructL(); + CActiveScheduler::Add(this); + iGAVDPStateMachine = &aGAVDPStateMachine; + } + + +CGavdpTimeout::CGavdpTimeout() : CTimer(EPriorityLow) + { + } + + +void CGavdpTimeout::StartTimer(TTimeIntervalMicroSeconds32 aTimeInterval) + { + Cancel(); //just in case + After(aTimeInterval); + } + + +void CGavdpTimeout::RunL() + { + //the GAVDP callback has timed out - check the GAVDP state machine + //is in the connected state to cover ourselves in the event of a race + //condition + if ((iGAVDPStateMachine->State() == TGAVDPState::EConnectedToGavdp)|| + (iGAVDPStateMachine->State() == TGAVDPState::ESEPsDiscovered)) + { + iGAVDPStateMachine->CancelChangeState(KErrTimedOut); + } + } + + +