sl@0: // Copyright (c) 2005-2009 Nokia Corporation and/or its subsidiary(-ies). sl@0: // All rights reserved. sl@0: // This component and the accompanying materials are made available sl@0: // under the terms of "Eclipse Public License v1.0" sl@0: // which accompanies this distribution, and is available sl@0: // at the URL "http://www.eclipse.org/legal/epl-v10.html". sl@0: // sl@0: // Initial Contributors: sl@0: // Nokia Corporation - initial contribution. sl@0: // sl@0: // Contributors: sl@0: // sl@0: // Description: sl@0: // sl@0: sl@0: #include sl@0: #include "mmfSBCCodecImplementationUIDs.hrh" sl@0: #include sl@0: #include "A2dpCodecUtilities.h" sl@0: #include "A2dpBTheadsetAudioIf.h" sl@0: #include "MMFBtRoutingSoundDevice.h" // for TRange sl@0: sl@0: /** sl@0: Panics sl@0: **/ sl@0: enum TA2dpBTHeadsetPanic sl@0: { sl@0: EA2dpBTHeadsetUnExpectedState,//0 sl@0: EA2dpBTHeadsetNoRTPStreamer,//1 sl@0: EA2dpBTHeadsetNoData,//2 sl@0: EA2dpBTHeadsetUnexpectedDataType,//3 sl@0: EA2dpBTHeadsetBTAddrNotSet, //4 sl@0: EA2dpBTHeadsetNoCodec //5 sl@0: }; sl@0: sl@0: sl@0: static void Panic(TA2dpBTHeadsetPanic aPanic) sl@0: // Panic client sl@0: { sl@0: _LIT(KA2dpBTHeadsetPanicName, "A2DP BT If Panic"); sl@0: User::Panic(KA2dpBTHeadsetPanicName, aPanic); sl@0: } sl@0: sl@0: sl@0: CA2dpBTHeadsetAudioInterface::CA2dpBTHeadsetAudioInterface() sl@0: { sl@0: } sl@0: sl@0: void CA2dpBTHeadsetAudioInterface::ConstructL() sl@0: { sl@0: iA2dpCodecConfiguration = CA2dpAudioCodecConfiguration::NewL(); sl@0: User::LeaveIfError(iSocketServer.Connect()); sl@0: iGAVDPStateMachine = CGAVDPStateMachine::NewL(*this, *iA2dpCodecConfiguration, iSocketServer); sl@0: } sl@0: sl@0: /** sl@0: Creates CA2dpBTHeadsetAudioInterface sl@0: Note there should only ever be one CA2dpBTHeadsetAudioInterface* sl@0: However this being a singleton is not enforced in this class however sl@0: The client of this class ie the CA2dpBTHeadsetAudioServer should enforce this. sl@0: sl@0: @return CA2dpBTHeadsetAudioInterface* sl@0: */ sl@0: EXPORT_C CA2dpBTHeadsetAudioInterface* CA2dpBTHeadsetAudioInterface::NewL() sl@0: { sl@0: CA2dpBTHeadsetAudioInterface* self = new(ELeave) CA2dpBTHeadsetAudioInterface(); sl@0: CleanupStack::PushL(self); sl@0: self->ConstructL(); sl@0: CleanupStack::Pop(self); sl@0: return self; sl@0: } sl@0: sl@0: /** sl@0: destructor sl@0: */ sl@0: EXPORT_C CA2dpBTHeadsetAudioInterface::~CA2dpBTHeadsetAudioInterface() sl@0: { sl@0: SetSniffMode(ETrue); sl@0: delete iGAVDPStateMachine; sl@0: delete iCodec; sl@0: delete iA2dpCodecConfiguration; sl@0: delete iRTPStreamer; sl@0: iSocketServer.Close(); sl@0: } sl@0: sl@0: sl@0: /** sl@0: Procedure to perform a GAVDP initialization sequence on an A2DP headset sl@0: If the headset is already initialized then this procedure does not perform sl@0: any further initialization. sl@0: if link has gone down since a previous call to Initialize then sl@0: calling this function will perform a full reinitialize. sl@0: if link goes down during initialization will generate error sl@0: The GAVDPStateChangeComplete callback is called when the headset device sl@0: is in the open state. sl@0: The headset is in the GAVDP_Open state on completion sl@0: It is not possible to perform an Initialize if the headset is already in the sl@0: streaming state or is already being initialized. sl@0: sl@0: @param aRemoteAddress Address of the bluetooth headset we are trying to initialize sl@0: @param aStatus sl@0: */ sl@0: EXPORT_C void CA2dpBTHeadsetAudioInterface::Initialize(const TBTDevAddr& aRemoteAddress, TRequestStatus& aStatus) sl@0: { sl@0: __ASSERT_ALWAYS(((iGAVDPStateMachine->State() == TGAVDPState::EGAVDPIdle)|| sl@0: (iGAVDPStateMachine->State() == TGAVDPState::EGAVDPOpen)), Panic(EA2dpBTHeadsetUnExpectedState)); sl@0: __ASSERT_ALWAYS(!iReconfiguring, Panic(EA2dpBTHeadsetUnExpectedState)); //can't initialize while we're reconfiguring sl@0: iInitializeRequestStatus = &aStatus; sl@0: *iInitializeRequestStatus = KRequestPending; sl@0: sl@0: //this will cause a full initialization if the address has changed sl@0: iGAVDPStateMachine->SetBTAddress(aRemoteAddress); sl@0: sl@0: TGAVDPState state(TGAVDPState::EGAVDPOpen); sl@0: TInt err = iGAVDPStateMachine->ChangeState(state); sl@0: if (err) sl@0: { sl@0: User::RequestComplete(iInitializeRequestStatus, err); sl@0: } sl@0: } sl@0: sl@0: sl@0: /** sl@0: Procedure to cancel a GAVDP initialization sequence on an A2DP headset sl@0: */ sl@0: EXPORT_C void CA2dpBTHeadsetAudioInterface::CancelInitialize() sl@0: { sl@0: if (iInitializeRequestStatus) sl@0: { sl@0: if (*iInitializeRequestStatus == KRequestPending)//make sure there is a pending request to cancel sl@0: { sl@0: iGAVDPStateMachine->CancelChangeState(); sl@0: } sl@0: } sl@0: } sl@0: sl@0: sl@0: /** sl@0: Handles the state change GAVDPIdle->GAVDPOpen sl@0: This state change usually occurs as a result to an Initialize call sl@0: Can also occur due to an error condition resulting in a GAVDP state machine reset sl@0: */ sl@0: void CA2dpBTHeadsetAudioInterface::ProcessGAVDPStateChangeIdleToOpen(TInt aError) sl@0: { sl@0: __ASSERT_DEBUG((aError||iGAVDPStateMachine->State()==TGAVDPState::EGAVDPOpen|| sl@0: iGAVDPStateMachine->State()==TGAVDPState::EGAVDPIdle),Panic(EA2dpBTHeadsetUnExpectedState)); //if there is no error then current state should be open sl@0: sl@0: if (!aError) sl@0: { sl@0: SetSniffMode(ETrue); sl@0: iPaused = EFalse; sl@0: iReconfiguring = EFalse; sl@0: if (iRTPStreamer) //if this is true then an error has occured somewhere sl@0: {//and we need to reset the streamer and close the socket sl@0: delete iRTPStreamer; sl@0: iRTPStreamer = NULL; sl@0: iGAVDPStateMachine->BearerSocket().Close(); sl@0: } sl@0: } sl@0: sl@0: if (iInitializeRequestStatus) sl@0: { sl@0: if (*iInitializeRequestStatus == KRequestPending) sl@0: { sl@0: User::RequestComplete(iInitializeRequestStatus, aError); sl@0: } sl@0: } sl@0: } sl@0: sl@0: sl@0: /** sl@0: Handles the state change GAVDPOpen->GAVDPStreaming sl@0: This state change occurs as a result to an OpenDevice call sl@0: This can get called twice, once if a reconfiguration is needed prior sl@0: to going in the streaming state where the state machine should still be in the open state sl@0: and a second time when the state machine is in the streaming state sl@0: */ sl@0: void CA2dpBTHeadsetAudioInterface::ProcessGAVDPStateChangeOpenToStreaming(TInt aError) sl@0: { sl@0: if (iOpenDeviceRequestStatus) sl@0: { sl@0: iOpenDeviceError = aError; sl@0: if (*iOpenDeviceRequestStatus == KRequestPending) sl@0: { sl@0: if (iReconfiguring) sl@0: {//should be in the OPEN state if we are reconfiguring sl@0: __ASSERT_DEBUG((iGAVDPStateMachine->State() == TGAVDPState::EGAVDPOpen),Panic(EA2dpBTHeadsetUnExpectedState)); sl@0: iReconfiguring = EFalse; sl@0: if (!iOpenDeviceError) sl@0: { sl@0: //move GAVDP state to streaming sl@0: TGAVDPState state(TGAVDPState::EGAVDPStreaming); sl@0: iOpenDeviceError = iGAVDPStateMachine->ChangeState(state); sl@0: } sl@0: if (iOpenDeviceError) sl@0: { sl@0: SetSniffMode(ETrue); //go back to sniff mode sl@0: User::RequestComplete(iOpenDeviceRequestStatus, iOpenDeviceError); sl@0: } sl@0: }//if (iReconfiguring) sl@0: else sl@0: {//callback must be in response to a streaming change state sl@0: __ASSERT_DEBUG((aError||iGAVDPStateMachine->State()==TGAVDPState::EGAVDPStreaming),Panic(EA2dpBTHeadsetUnExpectedState)); //if there is no error then current state should be open sl@0: if (!iOpenDeviceError) sl@0: { sl@0: __ASSERT_DEBUG((iGAVDPStateMachine->State()==TGAVDPState::EGAVDPStreaming),Panic(EA2dpBTHeadsetUnExpectedState)); sl@0: delete iRTPStreamer; sl@0: iRTPStreamer = NULL; sl@0: TRAP(iOpenDeviceError,iRTPStreamer = CActiveRTPStreamer::NewL(iGAVDPStateMachine->BearerSocket(),*this)); sl@0: if (!iOpenDeviceError) sl@0: {//create codec sl@0: iAudioSettingsHaveChanged = EFalse; sl@0: iRTPStreamer->SetAudioConfiguration(*iA2dpCodecConfiguration); sl@0: //if we have not set the data type ourselves then get iDataType sl@0: //from the code configuration sl@0: if (iDataType == KMMFFourCCCodeNULL) sl@0: { sl@0: iDataType = iA2dpCodecConfiguration->HeadsetCodecDataType(); sl@0: if (iDataType == KMMFFourCCCodeSBC) sl@0: {//then the audio interfacef code will be pcm16 sl@0: iDataType = KMMFFourCCCodePCM16; sl@0: } sl@0: } sl@0: if (iDataType == KMMFFourCCCodePCM16) sl@0: {//then an SBC codec is required sl@0: const TUid KSbcCodecUid = { KMmfUidCodecPCM16ToSBC }; sl@0: TRAP(iOpenDeviceError,iCodec = CMMFCodec::NewL(KSbcCodecUid)); sl@0: if (!iOpenDeviceError) sl@0: { sl@0: TUid configType; sl@0: configType.iUid = KMmfUidSBCConfigure; sl@0: TSBCFrameParameters& SBCFrameParameters = iA2dpCodecConfiguration->UpdateLocalSBCCodecConfiguration(); sl@0: //use a package buffer for codec config to keep flexability should sl@0: //it be possible to use other codecs rather than SBC in future. sl@0: TPckgBuf SBCFrameParametersBuf(SBCFrameParameters); sl@0: TRAP(iOpenDeviceError,iCodec->ConfigureL(configType, SBCFrameParametersBuf)); sl@0: iRTPStreamer->SetCodec(*iCodec); sl@0: } sl@0: } sl@0: }//if (!iOpenDeviceError) sl@0: }//if (!iOpenDeviceError) sl@0: if ((iOpenDeviceError) && (iGAVDPStateMachine->State()==TGAVDPState::EGAVDPStreaming)) sl@0: { sl@0: //there was an error so wind back state to EGAVDPOpen sl@0: //don't complete the OpenDevice request status till we've wound back sl@0: TGAVDPState state(TGAVDPState::EGAVDPOpen); sl@0: TInt iOpenDeviceError = iGAVDPStateMachine->ChangeState(state); sl@0: //if the above fails there's not much we can do sl@0: } sl@0: else sl@0: { sl@0: //if we get to here then we should either be in the streaming state with no errors sl@0: //or in the open state with an error - either way we complete the OpenDevice request status sl@0: User::RequestComplete(iOpenDeviceRequestStatus, iOpenDeviceError); sl@0: if (iOpenDeviceError) sl@0: { sl@0: SetSniffMode(ETrue); sl@0: } sl@0: } sl@0: }//else if not (iReconfiguring) sl@0: }// if (*iOpenRequestStatus == KRequestPending) sl@0: }//if (iOpenRequestStatus) sl@0: } sl@0: sl@0: sl@0: /* sl@0: Handles the state change GAVDPStreaming->GAVDPOpen sl@0: The transition from streaming to open can be caused by one of two ways sl@0: 1 - A call to CloseDevice sl@0: 2 - A call to Open device where there was an error after the transition to streaming sl@0: */ sl@0: void CA2dpBTHeadsetAudioInterface::ProcessGAVDPStateChangeStreamingToOpen(TInt aError) sl@0: { sl@0: __ASSERT_DEBUG((aError||iGAVDPStateMachine->State()==TGAVDPState::EGAVDPOpen),Panic(EA2dpBTHeadsetUnExpectedState)); //if there is no error then current state should be open sl@0: sl@0: //only have streamer and codec in streaming state sl@0: delete iRTPStreamer; sl@0: iRTPStreamer = NULL; sl@0: delete iCodec; sl@0: iCodec = NULL; sl@0: sl@0: if (iCloseDeviceRequestStatus) sl@0: {//state change is in response to a close device sl@0: if (*iCloseDeviceRequestStatus == KRequestPending) sl@0: { sl@0: User::RequestComplete(iCloseDeviceRequestStatus, aError); sl@0: } sl@0: } sl@0: else if (iOpenDeviceRequestStatus) sl@0: { sl@0: //it must be a failed OpenDevice where the state has been wound back to EGAVDPOpen sl@0: __ASSERT_DEBUG((iGAVDPStateMachine->State() == TGAVDPState::EGAVDPOpen),Panic(EA2dpBTHeadsetUnExpectedState)); sl@0: //the following ASSERT_DEBUG should be present but is commented out as the RVCT sl@0: //compiler generates a warning sl@0: //__ASSERT_DEBUG((iOpenDeviceError), EA2dpBTHeadsetUnExpectedState); sl@0: if (*iOpenDeviceRequestStatus == KRequestPending) sl@0: { sl@0: User::RequestComplete(iOpenDeviceRequestStatus, iOpenDeviceError); sl@0: } sl@0: } sl@0: SetSniffMode(ETrue); sl@0: } sl@0: sl@0: /** sl@0: Callback by the GAVDP state machine when a GAVDP state change has been completed sl@0: sl@0: @param aInitialState The TGAVDPState prior to the start of the state change sl@0: @param aCurrentState The current TGAVDPState of the GAVDP state machine sl@0: @param aError standard Symbian error code. sl@0: */ sl@0: void CA2dpBTHeadsetAudioInterface::GAVDPStateChangeComplete(const TGAVDPState& aInitialState, TInt aError) sl@0: { sl@0: switch(aInitialState.State()) sl@0: { sl@0: case TGAVDPState::EGAVDPIdle: sl@0: ProcessGAVDPStateChangeIdleToOpen(aError); sl@0: break; sl@0: case TGAVDPState::EGAVDPOpen: sl@0: ProcessGAVDPStateChangeOpenToStreaming(aError); sl@0: break; sl@0: case TGAVDPState::EGAVDPStreaming: sl@0: ProcessGAVDPStateChangeStreamingToOpen(aError); sl@0: break; sl@0: default: sl@0: Panic(EA2dpBTHeadsetUnExpectedState); sl@0: break; sl@0: } sl@0: } sl@0: sl@0: sl@0: /** sl@0: Returns a list of the supported datatypes for audio that can be sent to the headset. sl@0: The list is obtained from the headset, so the headset need to have been Initialized first sl@0: sl@0: @param aSupportedDataTypes sl@0: The array of supported data types that will be filled in by this function. sl@0: The supported data types are in the form of an array sl@0: of TFourCC codes. Any existing entries in the array will be overwritten on sl@0: calling this function. sl@0: @return standard Symbian error code. sl@0: KErrNotReady if headset has not been initialized. sl@0: */ sl@0: EXPORT_C TInt CA2dpBTHeadsetAudioInterface::GetSupportedDataTypes(RArray& aSupportedDataTypes) const sl@0: { sl@0: aSupportedDataTypes.Reset(); //clear any existing data in aSupportedDataTypes array sl@0: TInt err = KErrNone; sl@0: RArray& usableSEPs = iGAVDPStateMachine->UsableSEPs(); sl@0: TUint numberOfUsableSEPS = usableSEPs.Count(); sl@0: if (numberOfUsableSEPS) sl@0: { sl@0: TUint i; sl@0: TUsableSEP SEP; sl@0: //iterate through the list of usable SEPs looking for one that supports sl@0: //the requested data type sl@0: for (i=0; iState() == TGAVDPState::EGAVDPIdle) sl@0: {//no usable SEPs available so can't yet get supported data types sl@0: err = KErrNotReady; sl@0: } sl@0: sl@0: if (err) sl@0: { sl@0: aSupportedDataTypes.Reset(); sl@0: } sl@0: return err; sl@0: } sl@0: sl@0: sl@0: /** sl@0: Returns the sample rates supported by the headset. sl@0: In the case of SBC the supported values are obtained from the headset. sl@0: In the case of mp3,AAC & ATRAC 3 the values are not obtained from sl@0: the headset as the underlying bluetoothav code does not have a defined structure sl@0: for containing these values, therefore the mandatory values are returned sl@0: sl@0: @param aSupportedDiscreteRates sl@0: The array of supported sample rates that will be filled in by this function. sl@0: The supported data types are in the form of an array sl@0: of TFourCC codes. Any existing entries in the array will be overwritten on sl@0: calling this function. sl@0: @param aSupportedRateRanges sl@0: To cover the case where headsets support a range of sample rates sl@0: @return standard Symbian error code. sl@0: KErrNotReady if headset has not been initialized. sl@0: */ sl@0: EXPORT_C TInt CA2dpBTHeadsetAudioInterface::GetSupportedSampleRates(RArray& aSupportedDiscreteRates, RArray& aSupportedRateRanges) const sl@0: { sl@0: TInt error = KErrNone; sl@0: aSupportedDiscreteRates.Reset(); sl@0: aSupportedRateRanges.Reset(); sl@0: sl@0: //get the codec capabilites from the GAVDP state machine sl@0: TAvdtpMediaCodecCapabilities* codecCaps = NULL; sl@0: error = iGAVDPStateMachine->CodecCaps(codecCaps); sl@0: sl@0: if (!error) sl@0: { sl@0: error = TA2dpCodecCapabilityParser::GetSupportedSampleRates(*codecCaps,aSupportedDiscreteRates); sl@0: } sl@0: if (error) sl@0: { sl@0: aSupportedDiscreteRates.Reset(); sl@0: } sl@0: sl@0: return error; sl@0: } sl@0: sl@0: sl@0: /** sl@0: Returns the number of channels supported by the headset. sl@0: In the case of SBC the supported values are obtained from the headset. sl@0: In the case of mp3,AAC & ATRAC 3 the values are not obtained from sl@0: the headset as the underlying bluetoothav code does not have a defined structure sl@0: for containing these values, therefore the mandatory values are returned sl@0: sl@0: @param aSupportedChannels sl@0: The array of supported number of channels that will be filled in by this function. sl@0: Any existing entries in the array will be overwritten on sl@0: calling this function. sl@0: @param aStereoSupport sl@0: Additional parameter to specifiy the stereo support sl@0: @return standard Symbian error code. sl@0: @see TMMFStereoSupport sl@0: KErrNotReady if headset has not been initialized. sl@0: */ sl@0: EXPORT_C TInt CA2dpBTHeadsetAudioInterface::GetSupportedChannels(RArray& aSupportedChannels, TMMFStereoSupport& aStereoSupport) const sl@0: { sl@0: aSupportedChannels.Reset(); sl@0: aStereoSupport = EMMFNone; sl@0: sl@0: TInt error = KErrNone; sl@0: sl@0: //get the codec capabilites from the GAVDP state machine sl@0: TAvdtpMediaCodecCapabilities* codecCaps = NULL; sl@0: error = iGAVDPStateMachine->CodecCaps(codecCaps); sl@0: sl@0: if (!error) sl@0: { sl@0: error = TA2dpCodecCapabilityParser::GetSupportedChannels(*codecCaps, aSupportedChannels, aStereoSupport); sl@0: } sl@0: if (error) sl@0: { sl@0: aSupportedChannels.Reset(); sl@0: aStereoSupport = EMMFNone; sl@0: } sl@0: return error; sl@0: } sl@0: sl@0: sl@0: /** sl@0: Sets the data type of the data sent to the CA2dpBTHeadsetAudioInterface sl@0: Note that data type specified here may not be identical to the data type sl@0: sent to the headset and hence the data type used in the the a2dp codec configuration. sl@0: ie if the CA2dpBTHeadsetAudioInterface data type set by this method is pcm16 sl@0: then the data type sent to the headset is SBC. sl@0: note that this method just sets the internal iDatatype member sl@0: the datatype cannot be changed on the fly, but only gets changed sl@0: when the GAVDP state machine is reconfigured in CA2dpBTHeadsetAudioInterface::OpenDevice() sl@0: sl@0: @param aSinkFourCC sl@0: The 4CC code of the data to be supplied to this class instance. sl@0: sl@0: @return System wide error code indicating if the function call was successful. sl@0: KErrNotReady if the headset is not initialized sl@0: KErrInUse if the headset is already streaming sl@0: KErrNotSupported if the datatype is not supported sl@0: */ sl@0: EXPORT_C TInt CA2dpBTHeadsetAudioInterface::SetDataType(const TFourCC& aDataType) sl@0: { sl@0: TInt error = KErrNone; sl@0: if (iGAVDPStateMachine->State() == TGAVDPState::EGAVDPIdle) sl@0: { sl@0: return KErrNotReady; sl@0: } sl@0: if (iGAVDPStateMachine->State() == TGAVDPState::EGAVDPStreaming) sl@0: { sl@0: return KErrInUse; //don't allow this while we're streaming audio sl@0: } sl@0: sl@0: //the requested data type is a non standard data type sl@0: //so first check if there is a SEP that supports it sl@0: RArray& usableSEPs = iGAVDPStateMachine->UsableSEPs(); sl@0: TUint i; sl@0: TUsableSEP SEP; sl@0: TBool dataTypeSupported = EFalse; sl@0: //iterate through the list of usable SEPs looking for one that supports sl@0: //the requested data type sl@0: for (i=0; iHeadsetCodecDataType()) sl@0: { sl@0: iA2dpCodecConfiguration->SetHeadsetCodecDataType(aDataType); sl@0: iAudioSettingsHaveChanged = ETrue; sl@0: } sl@0: break; sl@0: } sl@0: else if ((SEP.iDataType == KMMFFourCCCodeSBC) && (aDataType == KMMFFourCCCodePCM16)) sl@0: { sl@0: dataTypeSupported = ETrue; sl@0: iDataType = aDataType; sl@0: if (iA2dpCodecConfiguration->HeadsetCodecDataType()!= KMMFFourCCCodeSBC) sl@0: { sl@0: iA2dpCodecConfiguration->SetHeadsetCodecDataType(KMMFFourCCCodeSBC); sl@0: iAudioSettingsHaveChanged = ETrue; sl@0: } sl@0: break; sl@0: } sl@0: } sl@0: if (!dataTypeSupported) sl@0: { sl@0: error = KErrNotSupported; sl@0: } sl@0: return error; sl@0: } sl@0: sl@0: sl@0: /** sl@0: Sets the sample rate sl@0: sl@0: @param aSampleRate sl@0: @return System wide error code indicating if the function call was successful. sl@0: KErrNotReady if the headset is not initialized sl@0: KErrInUse if the headset is already streaming sl@0: KErrNotSupported if the sample rate is not supported sl@0: */ sl@0: EXPORT_C TInt CA2dpBTHeadsetAudioInterface::SetSampleRate(TUint aSampleRate) sl@0: { sl@0: TInt error = KErrNone; sl@0: if (iGAVDPStateMachine->State() == TGAVDPState::EGAVDPIdle) sl@0: { sl@0: return KErrNotReady; sl@0: } sl@0: if (iGAVDPStateMachine->State() == TGAVDPState::EGAVDPStreaming) sl@0: { sl@0: //don't allow this while we're streaming audio sl@0: return KErrInUse; sl@0: } sl@0: sl@0: //check that the sample rate is supported sl@0: RArray supportedDiscreteRates; sl@0: RArray supportedRateRanges; sl@0: GetSupportedSampleRates(supportedDiscreteRates, supportedRateRanges); sl@0: if (supportedDiscreteRates.Find(aSampleRate) == KErrNotFound) sl@0: { sl@0: //in theory we should iterate through the ranges as well, however sl@0: //SBC only suports discrete values so just return KErrNotSupported sl@0: error = KErrNotSupported; sl@0: } sl@0: else sl@0: { sl@0: if (aSampleRate != iA2dpCodecConfiguration->SampleRate()) sl@0: { sl@0: iA2dpCodecConfiguration->SetSampleRate(aSampleRate); sl@0: iAudioSettingsHaveChanged = ETrue; sl@0: } sl@0: } sl@0: supportedDiscreteRates.Close(); sl@0: supportedRateRanges.Close(); sl@0: return error; sl@0: } sl@0: sl@0: sl@0: /** sl@0: Sets the number of channels sl@0: sl@0: @param aSampleChannels sl@0: @param aStereoSupport. note that the aStereoSupport in the case of pcm16 refers to the output to the headset. sl@0: the data going for pcm16 is always either interleaved stereo pcm16 or mono sl@0: the aStereoSupport parameter is ignored for non stereo sl@0: @see TMMFStereoSupport sl@0: @return System wide error code indicating if the function call was successful. sl@0: KErrNotReady if the headset is not initialized sl@0: KErrInUse if the headset is already streaming sl@0: KErrNotSupported if the datatype is not supported sl@0: */ sl@0: EXPORT_C TInt CA2dpBTHeadsetAudioInterface::SetChannels(TUint aChannels, TMMFStereoSupport aStereoSupport) sl@0: { sl@0: TInt error = KErrNone; sl@0: if (iGAVDPStateMachine->State() == TGAVDPState::EGAVDPIdle) sl@0: { sl@0: return KErrNotReady; sl@0: } sl@0: if (iGAVDPStateMachine->State() == TGAVDPState::EGAVDPStreaming) sl@0: { sl@0: return KErrInUse; //don't allow this while we're streaming audio sl@0: } sl@0: sl@0: //check that the number of channels is supported sl@0: RArray supportedChannels; sl@0: TMMFStereoSupport stereoSupport; sl@0: GetSupportedChannels(supportedChannels, stereoSupport); sl@0: if (supportedChannels.Find(aChannels) == KErrNotFound) sl@0: { sl@0: error = KErrNotSupported; sl@0: } sl@0: else if (aChannels == EMMFStereo) sl@0: { sl@0: //now check stereo support sl@0: if ((aStereoSupport & stereoSupport) != aStereoSupport) sl@0: { sl@0: error = KErrNotSupported; sl@0: } sl@0: else if (iA2dpCodecConfiguration->StereoSupport() != aStereoSupport) sl@0: { sl@0: iA2dpCodecConfiguration->SetStereoSupport(aStereoSupport); sl@0: iAudioSettingsHaveChanged = ETrue; sl@0: } sl@0: } sl@0: if (!error) sl@0: { sl@0: if (iA2dpCodecConfiguration->Channels() != aChannels) sl@0: { sl@0: iA2dpCodecConfiguration->SetChannels(aChannels); sl@0: iAudioSettingsHaveChanged = ETrue; sl@0: } sl@0: } sl@0: return error; sl@0: } sl@0: sl@0: sl@0: /** sl@0: Procedure to gets the headset into the GAVDP Streaming state where sl@0: it is ready to accept audio data. The procedure causes a reconfiguration sl@0: of the headset incase the settings have changed - and if they haven't then the sl@0: reconfigure does nothing. sl@0: The GAVDPStateChangeComplete callback is called after the reconfigure sl@0: where it puts the GAVDP state machine in the streaming state sl@0: The GAVDPStateChangeComplete callback is called again when the headset device sl@0: is in the streaming state. sl@0: sl@0: @param aStatus sl@0: */ sl@0: EXPORT_C void CA2dpBTHeadsetAudioInterface::OpenDevice(TRequestStatus& aStatus) sl@0: { sl@0: iOpenDeviceRequestStatus = &aStatus; sl@0: *iOpenDeviceRequestStatus = KRequestPending; sl@0: sl@0: if (iGAVDPStateMachine->State() == TGAVDPState::EGAVDPStreaming) sl@0: { sl@0: //device is already open sl@0: User::RequestComplete(iOpenDeviceRequestStatus, KErrNone); sl@0: return; sl@0: } sl@0: sl@0: //this isn't a local error variable as it is used in the GAVDPStateChangeComplete callback sl@0: iOpenDeviceError = KErrNone; sl@0: //take out of sniff mode - no point in checking error code as there is sl@0: //not much that can be done sl@0: SetSniffMode(EFalse); sl@0: sl@0: //first reconfigure if the configuration has changed sl@0: //note that there is a subtle difference between the configuration of the sl@0: //CA2dpBTHeadsetAudioInterface and that of the GAVDP state machine sl@0: //the configuration of the CA2dpBTHeadsetAudioInterface is the configuration sl@0: //seen from the DevSound whereas the CGAVDPStateMachine is the configuration sl@0: //as seen by the headset. In most cases they would be identical, the only sl@0: //current case where the differ is in the data type where the DevSound sees sl@0: //the data tpye as pcm16 but the headset is SBC sl@0: iOpenDeviceError = iGAVDPStateMachine->Reconfigure(iAudioSettingsHaveChanged); sl@0: if (iOpenDeviceError) sl@0: { sl@0: User::RequestComplete(iOpenDeviceRequestStatus, iOpenDeviceError); sl@0: } sl@0: else sl@0: { sl@0: //the Reconfigure will result in a GAVDPStateChangeComplete callback sl@0: iReconfiguring = ETrue; sl@0: } sl@0: } sl@0: sl@0: sl@0: /** sl@0: Procedure to cancel a GAVDP initialization sequence on an A2DP headset sl@0: */ sl@0: EXPORT_C void CA2dpBTHeadsetAudioInterface::CancelOpenDevice() sl@0: { sl@0: if (iOpenDeviceRequestStatus) sl@0: { sl@0: if (*iOpenDeviceRequestStatus == KRequestPending)//make sure there is a pending request to cancel sl@0: { sl@0: iGAVDPStateMachine->CancelChangeState(); sl@0: } sl@0: } sl@0: } sl@0: sl@0: /** sl@0: Procedure to put a headset that is in the GAVDP Streaming state back into sl@0: the GAVDP Open state sl@0: The GAVDPStateChangeComplete callback is called again when the headset device sl@0: is in the open state. sl@0: sl@0: @param aStatus sl@0: */ sl@0: EXPORT_C void CA2dpBTHeadsetAudioInterface::CloseDevice(TRequestStatus& aStatus) sl@0: { sl@0: iCloseDeviceRequestStatus = &aStatus; sl@0: *iCloseDeviceRequestStatus = KRequestPending; sl@0: sl@0: if (iGAVDPStateMachine->State() != TGAVDPState::EGAVDPStreaming) sl@0: { sl@0: //device isn't open sl@0: User::RequestComplete(iCloseDeviceRequestStatus, KErrNone); sl@0: return; sl@0: } sl@0: else sl@0: { sl@0: if (iRTPStreamer) sl@0: { sl@0: iRTPStreamer->Pause(); sl@0: } sl@0: //note the callback ProcessGAVDPStateChangeStreamingToOpen sl@0: //will put the BT link back in sniff mode sl@0: TGAVDPState state(TGAVDPState::EGAVDPOpen); sl@0: TInt err = iGAVDPStateMachine->ChangeState(state); sl@0: if (err) sl@0: { sl@0: User::RequestComplete(iCloseDeviceRequestStatus, err); sl@0: } sl@0: } sl@0: } sl@0: sl@0: /** sl@0: Procedure to get the headset volume sl@0: sl@0: @return volume in the range 0-255 sl@0: */ sl@0: EXPORT_C TUint CA2dpBTHeadsetAudioInterface::Volume() const sl@0: { sl@0: return 0; sl@0: } sl@0: sl@0: sl@0: /** sl@0: Procedure to set the volume in the range 0-255 sl@0: Note this procedure requires the AVRCP bluettooth profile sl@0: in order to change the volume which is not implemented sl@0: sl@0: @param aVolume the volume in the range 0-255 sl@0: */ sl@0: EXPORT_C TInt CA2dpBTHeadsetAudioInterface::SetVolume(TUint /*aVolume*/) sl@0: { sl@0: return KErrNone; sl@0: } sl@0: sl@0: sl@0: /** sl@0: This function causes the CA2dpBTHeadsetAudioInterface to send the audio data contained in aData to the initialized A2DP headset. sl@0: The data is buffered internally and CA2dpBTHeadsetAudioInterface may already be playing audio sl@0: when this method is called. sl@0: The TRequestStatus is completed when the CA2dpBTHeadsetAudioInterface has finished with the audio data contained in aData. sl@0: By 'finished' this does not mean finished as in physically finished playing the audio, sl@0: but to inform the client that: sl@0: a) it can repopulate the buffer with new audio data. sl@0: b) the CA2dpBTHeadsetAudioInterface is ready to accept another buffer of audio data via a further call to PlayData() sl@0: (ie the CA2dpBTHeadsetAudioInterface is not expected to accept multiple buffers in succession prior to completing their sl@0: TRequestStatus as this would not provide the client with any indication of whether the sl@0: sound drivers internal buffers are full). sl@0: sl@0: @param aStatus The TRequestStatus completed when the sound driver has finished with the audio data contained in aData. sl@0: The TRequestStatus is set to KErrNone unless the request has been cancelled via CancelPlayData() sl@0: in which case the status will be set to KErrCancel. sl@0: The TRequestStatus is completed as soon as possible and before the audio has been physically played sl@0: in order for the client to send further audio buffers before the buffers already in the sound driver sl@0: are played out. sl@0: Prior to calling PlayData() the client would be expected to Initialize Configure and Open the CA2dpBTHeadsetAudioInterface sl@0: and register with the sound driver that it requires notification of error conditions via a call to NotifyPlayError(). sl@0: sl@0: @param aData Buffer containing the audio data to be played. sl@0: aData, is not owned or created by the sound driver but is owned by the client. sl@0: */ sl@0: EXPORT_C void CA2dpBTHeadsetAudioInterface::PlayData(const TDesC8& aData, TRequestStatus& aStatus) sl@0: { sl@0: __ASSERT_DEBUG((aData.Length()), Panic(EA2dpBTHeadsetNoData)); sl@0: __ASSERT_DEBUG((iRTPStreamer), Panic(EA2dpBTHeadsetNoRTPStreamer)); sl@0: sl@0: iRTPStreamer->Send(aData, aStatus); sl@0: } sl@0: sl@0: /** sl@0: Cancels the current request for PlayData() if currently active. sl@0: The currently active PlayData() completes the request status immediately with KErrCancel. sl@0: */ sl@0: EXPORT_C void CA2dpBTHeadsetAudioInterface::CancelPlayData() sl@0: { sl@0: __ASSERT_DEBUG((iRTPStreamer), Panic(EA2dpBTHeadsetNoRTPStreamer)); sl@0: sl@0: iRTPStreamer->CancelLastSendBuffer(); sl@0: } sl@0: sl@0: sl@0: /** sl@0: Used to clear all internally buffered audio. sl@0: This function does not complete any pending TRequestStatuses. sl@0: */ sl@0: EXPORT_C void CA2dpBTHeadsetAudioInterface::FlushBuffer() sl@0: { sl@0: __ASSERT_DEBUG((iRTPStreamer), Panic(EA2dpBTHeadsetNoRTPStreamer)); sl@0: sl@0: iRTPStreamer->FlushPendingSendBuffers(); sl@0: } sl@0: sl@0: sl@0: /** sl@0: Returns the total number of bytes of data sent in PlayData that have been sl@0: sent to the headset. ie the number of bytes sent prior to any encoding by the SBC codec. sl@0: It is updated after each buffer has been sent to the headset. sl@0: The BytesPlayed are not reset to 0 on an error condition. sl@0: It must be explicitly reset via a call to ResetBytesPlayed(). sl@0: sl@0: @param The number of bytes played sl@0: */ sl@0: EXPORT_C TUint CA2dpBTHeadsetAudioInterface::BytesPlayed() const sl@0: { sl@0: TUint bytesPlayed = 0; sl@0: if (iRTPStreamer)//don't ASSERT iRTPStreamer here as this API still has meaning with no streamer sl@0: { sl@0: bytesPlayed = iRTPStreamer->BytesSent(); sl@0: } sl@0: return bytesPlayed; sl@0: } sl@0: sl@0: sl@0: /** sl@0: Resets bytes played to 0. sl@0: */ sl@0: EXPORT_C void CA2dpBTHeadsetAudioInterface::ResetBytesPlayed() sl@0: { sl@0: if (iRTPStreamer)//don't ASSERT iRTPStreamer here as this API still has meaning with no streamer sl@0: { sl@0: iRTPStreamer->ResetBytesSent(); sl@0: } sl@0: } sl@0: sl@0: sl@0: /** sl@0: Function to halt the sending of buffers to the headset. sl@0: Note that even if no buffers are being send via PlayData buffers sl@0: can still be sent to the headset as they are buffered internally sl@0: Calling PauseBuffer stops the internally buffered data being sent as well sl@0: as any buffers sent via PlayData sl@0: If the TRequestStatus from the previous PlayData() is outstanding then PausePlayBuffer() sl@0: does not complete the TRequestStatus. sl@0: The TRequestStatus will only be completed after ResumePlaying() is called. sl@0: */ sl@0: EXPORT_C void CA2dpBTHeadsetAudioInterface::PauseBuffer() sl@0: { sl@0: __ASSERT_DEBUG((iRTPStreamer), Panic(EA2dpBTHeadsetNoRTPStreamer)); sl@0: sl@0: iRTPStreamer->Pause(); sl@0: iPaused = ETrue; sl@0: } sl@0: sl@0: sl@0: /** sl@0: This resumes playing from where playing was halted from the previous call to PauseBuffer(). sl@0: The next TRequestStatus (if active) to be completed, is the request status from the previous PlayData() sl@0: prior to calling PausePlayBuffer(). sl@0: If PausePlayBuffer() has not been called prior to ResumePlaying(), sl@0: then this method does nothing. sl@0: */ sl@0: EXPORT_C void CA2dpBTHeadsetAudioInterface::ResumePlaying() sl@0: { sl@0: __ASSERT_DEBUG((iRTPStreamer), Panic(EA2dpBTHeadsetNoRTPStreamer)); sl@0: sl@0: iRTPStreamer->Resume(); sl@0: iPaused = EFalse; sl@0: } sl@0: sl@0: sl@0: /** sl@0: This function is used when the client is requesting notification of an error that occurs during playback. sl@0: The TRequestStatus passed in the NotifyPlayError() method is separate to the TRequestStatus passed into the PlayData() method, sl@0: and hence the client would typically have a separate active object for both the PlayData() sl@0: and NotifyPlayError() TRequestStatuses. sl@0: The NotifyPlayError() method is required because if an error occurs during playback, sl@0: the PlayData() TRequestStatus may not be active. sl@0: If a PlayData() request status is active then the error is reported back via the PlayData() sl@0: TRequestStatus and not via the NotifyPlayError() TRequestStatus. sl@0: If no PlayData() TRequestStatus is active then the error is reported back by completing the NotifyPlayError() sl@0: TRequestStatus with the appropriate error code. sl@0: Note that a call to CancelPlayData() should complete the PlayData() request status with KErrCancel sl@0: and should not complete the NotifyPlayError() TRequestStatus. sl@0: sl@0: @param aStatus TRequestStatus completed when a play error occurs. sl@0: */ sl@0: EXPORT_C void CA2dpBTHeadsetAudioInterface::NotifyError(TRequestStatus& aStatus) sl@0: { sl@0: iNotifyRequestStatus= &aStatus; sl@0: *iNotifyRequestStatus = KRequestPending; sl@0: } sl@0: sl@0: sl@0: /** sl@0: Cancels the NotifyPlayError() TRequestStatus if currently active. sl@0: The TRequestStatus completes immediately with KErrCancel. sl@0: */ sl@0: EXPORT_C void CA2dpBTHeadsetAudioInterface::CancelNotifyError() sl@0: { sl@0: User::RequestComplete(iNotifyRequestStatus, KErrCancel); sl@0: } sl@0: sl@0: sl@0: /** sl@0: Callback from the RTPStreamer when an unexpected event occurs sl@0: sl@0: @param aError sl@0: */ sl@0: void CA2dpBTHeadsetAudioInterface::RTPStreamerEvent(TInt aError) sl@0: { sl@0: //an error has occured while streaming sl@0: __ASSERT_DEBUG((iRTPStreamer), Panic(EA2dpBTHeadsetNoRTPStreamer)); sl@0: //could also be EGAVDPIdle as we could have just received a GAVDPStateMachineEvent sl@0: //which would reset the state to EGAVDPIdle sl@0: __ASSERT_DEBUG((iGAVDPStateMachine->State() == TGAVDPState::EGAVDPStreaming) || sl@0: (iGAVDPStateMachine->State() == TGAVDPState::EGAVDPIdle), Panic(EA2dpBTHeadsetUnExpectedState)); sl@0: TGAVDPState state(TGAVDPState::EGAVDPIdle); sl@0: TInt err = iGAVDPStateMachine->ChangeState(state); sl@0: //the above returns an error code but there's not much we can do if it returns an error sl@0: sl@0: if (iNotifyRequestStatus) sl@0: { sl@0: if (*iNotifyRequestStatus == KRequestPending) sl@0: { sl@0: User::RequestComplete(iNotifyRequestStatus, aError); sl@0: } sl@0: } sl@0: } sl@0: sl@0: sl@0: /** sl@0: Callback from the GAVDPStateMachine when an error event occurs sl@0: sl@0: @param aError sl@0: */ sl@0: void CA2dpBTHeadsetAudioInterface::GAVDPStateMachineEvent(TInt aError) sl@0: { sl@0: //an error has occured - if an error occurs then it could be sl@0: //because we've lost the connection to the headset eg because it is out of range sl@0: //has been switched off etc there we assume that the connection has been sl@0: //lost and put the GAVDP state back to idle sl@0: //note we're simplifying things somewhat as some errors may not be a result sl@0: //of a loss of connection to the headset sl@0: TGAVDPState state(TGAVDPState::EGAVDPIdle); sl@0: TInt err = iGAVDPStateMachine->ChangeState(state); sl@0: if (!iRTPStreamer) sl@0: {//if we don't have an RTP streamer sl@0: //then close the bearer socket. If we do have an RTP streamer sl@0: //then delay the close till after we have deleted the RTPStreamer in the sl@0: //the GAVDPStateChangeComplete callback. this is because sl@0: //closing the bearer socket before the RTP streamer is deleted sl@0: //can panic the RTP stack. sl@0: iGAVDPStateMachine->BearerSocket().Close(); sl@0: } sl@0: if (iNotifyRequestStatus) sl@0: { sl@0: if (*iNotifyRequestStatus == KRequestPending) sl@0: { sl@0: User::RequestComplete(iNotifyRequestStatus, aError); sl@0: } sl@0: } sl@0: } sl@0: sl@0: sl@0: /** sl@0: Callback from the GAVDPStateMachine when the headset has suspended the stream sl@0: */ sl@0: void CA2dpBTHeadsetAudioInterface::GAVDPStateMachineStreamSuspendedByRemoteHeadset() sl@0: { sl@0: if (iRTPStreamer) sl@0: { sl@0: iRTPStreamer->Pause(); sl@0: } sl@0: } sl@0: sl@0: sl@0: /** sl@0: Callback from the GAVDPStateMachine when the headset has resumed a suspended stream sl@0: */ sl@0: void CA2dpBTHeadsetAudioInterface::GAVDPStateMachineStreamResumedByRemoteHeadset() sl@0: { sl@0: if ((!iPaused)&&(iRTPStreamer)) sl@0: { sl@0: iRTPStreamer->Resume(); sl@0: } sl@0: } sl@0: sl@0: sl@0: /** sl@0: Callback from the GAVDPStateMachine when the headset has reconfigured the parameters sl@0: reconfigures codec with new configuration and inform RTP streamer sl@0: sl@0: @return SymbianOS error code sl@0: */ sl@0: TInt CA2dpBTHeadsetAudioInterface::GAVDPStateMachineReconfigureByRemoteHeadset() sl@0: { sl@0: //we're only allowing the headset to reconfigure for SBC SEP sl@0: __ASSERT_DEBUG(iA2dpCodecConfiguration->HeadsetCodecDataType() == KMMFFourCCCodeSBC, Panic( EA2dpBTHeadsetUnexpectedDataType)); sl@0: __ASSERT_DEBUG(iCodec,Panic(EA2dpBTHeadsetNoCodec)); sl@0: TUid configType; sl@0: TInt err = KErrNone; sl@0: configType.iUid = KMmfUidSBCConfigure; sl@0: TSBCFrameParameters& SBCFrameParameters = iA2dpCodecConfiguration->UpdateLocalSBCCodecConfiguration(); sl@0: TPckgBuf SBCFrameParametersBuf(SBCFrameParameters); sl@0: TRAP(err,iCodec->ConfigureL(configType, SBCFrameParametersBuf)); //update codec with new configuration sl@0: iRTPStreamer->SetAudioConfiguration(*iA2dpCodecConfiguration); //and tell RTP streamer sl@0: return err; sl@0: } sl@0: sl@0: sl@0: /** sl@0: Function to set/release the BT link into low power sniff mode sl@0: sl@0: @internalComponent sl@0: @return Error code, this is used for debugging, it's not checked sl@0: by the calling code as if SetSniffMode fails there's not much that can be sl@0: done in the calling code sl@0: */ sl@0: TInt CA2dpBTHeadsetAudioInterface::SetSniffMode(TBool aSniffMode) sl@0: { sl@0: TInt err = KErrNone; sl@0: sl@0: if (!iGAVDPStateMachine) sl@0: { sl@0: return KErrNotReady; sl@0: } sl@0: sl@0: if (iGAVDPStateMachine->BTAddress() == TBTDevAddr(0)) sl@0: { sl@0: return KErrNotReady; sl@0: } sl@0: sl@0: err = iBTPhysicalLinkAdapter.Open(iSocketServer, iGAVDPStateMachine->BTAddress()); sl@0: if (!err) sl@0: { sl@0: if (aSniffMode) sl@0: { sl@0: err = iBTPhysicalLinkAdapter.AllowLowPowerModes(ESniffMode); sl@0: err = iBTPhysicalLinkAdapter.ActivateSniffRequester(); sl@0: } sl@0: else sl@0: { sl@0: err = iBTPhysicalLinkAdapter.CancelLowPowerModeRequester(); sl@0: } sl@0: iBTPhysicalLinkAdapter.Close(); sl@0: } sl@0: sl@0: return err; sl@0: } sl@0: sl@0: sl@0: /** sl@0: This function should only used by the TSU_MMF_A2DPBLUETOOTH unit test harness sl@0: It allows the test harness to get at the MGavdpUser in order sl@0: to emulate actions from the headset sl@0: sl@0: @return the GavdpState machine as a MGavdpUser mixin sl@0: */ sl@0: EXPORT_C MGavdpUser* CA2dpBTHeadsetAudioInterface::TEST_MGavdpUser() sl@0: { sl@0: return static_cast(iGAVDPStateMachine); sl@0: } sl@0: sl@0: sl@0: /** sl@0: This function should only used by the TSU_MMF_A2DPBLUETOOTH unit test harness sl@0: It allows the test harness to override the configuration of the remote headset sl@0: for the SBC codec with regard to sampling frequencies, block size; subbands and allocation method sl@0: sl@0: @param sl@0: */ sl@0: EXPORT_C void CA2dpBTHeadsetAudioInterface::TEST_ForceRemoteSBCCodecConfiguration(const TSBCCodecCapabilities& aRemoteCodecConfiguration) sl@0: { sl@0: iA2dpCodecConfiguration->TEST_ForceRemoteSBCCodecConfiguration(aRemoteCodecConfiguration); sl@0: }