Channel Implementation

Describes how to implement the physical channels with the template port.

A Sound Driver PDD must implement physical channels to use the audio hardware of the phone. The main Sound Driver PDD class is derived from DSoundScPdd. A Sound Driver PDD that provides record and playback functions must implement a record driver channel and a playback driver channel.

The template defines two classes: DTemplateSoundScRxPdd and DTemplateSoundScTxPdd, corresponding to record and playback respectively. The classes provide default implementations for the virtual functions defined by DSoundScPdd, together with a constructor and destructor, and typically need little or no modification.

PDD class constructor

Implement the PDD class constructor for both the playback and record driver channels. This is normally limited to

  • initialising any data members that need values other than zero

  • initialising any DFC call-backs added.

Access to hardware has the potential to fail and so should be deferred to the second stage constructor DoCreate().

PDD class destructor

Implement the PDD class destructor for both the playback and record driver channels. The destructor must release any hardware and Symbian platform resources that have been allocated to the driver. For example, this might include:

  • unbinding ISRs

  • cancelling private DFCs

  • closing DMA channels

  • deleting any mono-to-stereo conversion buffers

The template versions of this function delete each DMA request object created in the PDD second stage constructor DSoundScPdd::DoCreate() and close the DMA channel.

DoCreate()

Implement a PDD class second stage constructor for both the playback and record driver channels. In the template version, the function DSoundScPdd::DoCreate() is called from the PDD factory when a channel is opened on the device. Generally, any hardware or Symbian platform resources required by the driver channel should be acquired here. However, powering up the sound device should be deferred to the PowerUp() function. Operations performed by this function include:

  • binding an ISR to an audio related interrupt

  • open a DMA channel

  • allocate a mono-to-stereo conversion buffer

The template versions of this function include code to set up a DMA channel. This involves opening a DMA channel in the appropriate direction for the driver channel and then allocating a set of DMA request objects. However, to use this implementation you must supply the missing platform specific information:

  • Provide values for the maximum number of DMA requests outstanding on the DMA channel. These values determine the number of separate DMA request objects allocated for the DMA channel, and so the number of transfer fragments that the PDD can accept from the LDD at any time. See playback and record.

  • Set the appropriate DMA values for your device within the file soundsc_plat.h. These values, renamed in section copying the template port implementation to reflect your device name, are KTemplateMaxTxDmaRequests or KTemplateMaxRxDmaRequests for playback and record respectively.

  • Setup the DMA channel information for the device. This includes the following members of TDmaChannel::SCreateInfo.

    • iCookie: The platform specific ID used by the DMA controller to select the DMA channel to open.

    • iDesCount: The number of DMA descriptors the DMA controller should create. This is typically set with the same value as KTemplateMaxTxDmaRequests or KTemplateMaxRxDmaRequests.

    • iDfcQ: The DFC queue to use to service DMA interrupts. This should point to the same DFC queue that is returned to the LDD for client request handling. See the DfcQ() section.

    • iDfcPriority: The DFC priority. This should be set to a higher value than that used by the LDD for handling client requests. For example, higher than one.

DfcQ()

Make sure that the template version of the DSoundScPdd::DfcQ() function is appropriate for your configuration. This function has the following signature:

TDfcQue* DfcQ()

The supplied implementation is as follows

TDfcQue* DTemplateSoundScTxPdd::DfcQ()
    {
    return(iPhysicalDevice->iDfcQPtr);
    }

Many requests are executed in the context of a kernel-side thread. Rather than assign a kernel thread for the driver in the LDD, the Sound Driver allows the PDD to specify the DFC thread returned via the DfcQ() function, which it calls when the driver channel is opened.

The default implementation for the record and playback driver channels, returns a pointer to the DFC queue created by the PDD factory class.

See also Implementing the PDD factory

GetChunkCreateInfo()

Implement the DSoundScPdd::GetChunkCreateInfo() function for both the playback and record driver channels. This function has the following signature:

void DSoundScPdd::GetChunkCreateInfo(TChunkCreateInfo& aChunkCreateInfo)

The supplied implementation is as follows:

void DTemplateSoundScTxPdd::GetChunkCreateInfo(TChunkCreateInfo& aChunkCreateInfo)
    {
    __KTRACE_SND(Kern::Printf(">DTemplateSoundScTxPdd::GetChunkCreateInfo"));

    // TO DO: (mandatory)
    // Setup the shared chunk create information in aChunkCreateInfo for this play device.
    aChunkCreateInfo.iType=TChunkCreateInfo::ESharedKernelMultiple;
    // aChunkCreateInfo.iMapAttr=???
    aChunkCreateInfo.iOwnsMemory=ETrue;                 // Using RAM pages.
    aChunkCreateInfo.iDestroyedDfc=NULL;                // No chunk destroy DFC.
    }

The PDD must initialise the TChunkCreateInfo object with the information that defines the characteristics of the shared chunk required for the audio device. This function is called by the LDD just before it creates a shared chunk for the channel, in response to an RSoundSc::SetBufferChunkCreate() request from the client.

Values for the following data members must be supplied by the PDD:

The data member TChunkCreateInfo::iMaxSize is calculated by the LDD so there is no need to define it.

See How to share chunks.

Caps()

Implement the DSoundScPdd::Caps() function for both the playback and record driver channels. The function has the following signature:

void DSoundScPdd::Caps(TDes8& aCapsBuf) const

The supplied implementation is as follows:

void DTemplateSoundScTxPdd::Caps(TDes8& aCapsBuf) const
    {
    __KTRACE_SND(Kern::Printf(">DTemplateSoundScTxPdd::Caps"));
    
    // Copy iCaps back.
    TPtrC8 ptr((const TUint8*)&iCaps,sizeof(iCaps));
    aCapsBuf.FillZ(aCapsBuf.MaxLength());
    aCapsBuf=ptr.Left(Min(ptr.Length(),aCapsBuf.MaxLength()));      
    }

TSoundFormatsSupportedV02 is the main audio capabilities class. The PDD must fill this object with the capabilities for the particular audio device. the LDD uses this to get the play or record capabilities of a particular sound device once a driver channel to the device has been opened.

Values for the following variables must be supplied by the PDD:

Many of the attribute ranges are passed as bit settings, and can assume all the values independently of one another.

The following is a portion of the Caps() function for the template port for the playback path of the AC97 Controller Unit (UCB140) codec device.

The PDD maintains a TSoundFormatsSupportedV02 object as one of its data members named iCaps. The contents of iCaps is copied into the descriptor and passed as an argument to the Caps() function:

void DTemplateSoundScTxPdd::Caps(TDes8& aCapsBuf) const
    {
     __KTRACE_SND(Kern::Printf(">DTemplateSoundScTxPdd::Caps"));
    
     // Copy iCaps back.
     TPtrC8 ptr((const TUint8*)&iCaps,sizeof(iCaps));
     aCapsBuf.FillZ(aCapsBuf.MaxLength());
     aCapsBuf=ptr.Left(Min(ptr.Length(),aCapsBuf.MaxLength()));        
    }

The data member iCaps is initialised by the function SetCaps() called from the second stage constructor of the PDD object:

void DTemplateSoundScTxPdd::SetCaps()
    {
     __KTRACE_SND(Kern::Printf(">DTemplateSoundScTxPdd::SetCaps"));
    
     // The data transfer direction for this unit is play.
     iCaps.iDirection=ESoundDirPlayback;
    
     // TO DO: (mandatory)
     // Setup the rest of the capabilities structure DTemplateSoundScTxPdd::iCaps with the capabilities of this
     // audio playback device.
    }

MaxTransferLen()

Implement the DSoundScPdd::MaxTransferLen() function for both playback and record driver channels. The function has the following signature:

TInt DSoundScPdd::MaxTransferLen() const

The supplied implementation is as follows:

TInt DTemplateSoundScTxPdd::MaxTransferLen() const
    {
    return(KTemplateMaxTxDmaTransferLen);
    }

This function is called each time the LDD alters the audio configuration of the channel. For example, after calling DSoundScPdd::SetConfig(). The LDD uses the value returned to fragment record and playback data transfers so that they are manageable by the PDD. See playback and record.

The value returned by MaxTransferLen() is not as important for PDDs that use the Symbian DMA framework because the DMA framework handles fragmentation.

If the PDD has to employ mono-to-stereo data conversion using a conversion buffer when configured in mono mode, then it needs to return the value that corresponds with the size of the conversion buffer each time it is configured in mono mode. See mono to stereo conversion.

PowerUp()

Implement the DSoundScPdd::PowerUp function for both the playback and record driver channels. The function has the following signature:

TInt DSoundScPdd::PowerUp()

The supplied implementation is as follows:

TInt DTemplateSoundScTxPdd::PowerUp()
    {
    // TO DO: (mandatory)
    // Power up the audio device.
    
    return(KErrNone);
    }

This function initialises the codec device and any associated controller hardware that allows the CPU to communicate with it. However, at this stage only basic initialisation of these hardware components is required, as the specific audio configuration has not yet been specified, and data transfer has not yet started.

If the PDD supports both record and playback driver channels then most, if not all, of this hardware initialisation is common to both channels. It may be necessary to ensure that such initialisation on one channel cannot interfere with the other. For example, when calling DSoundScPdd::PowerUp() on the record driver channel while the playback driver channel is active, make sure that it does not interfere with audio playback. This typically requires hardware status information to be held in the PDD factory object that is common to both channels.

SetConfig()

Implement the DSoundScPdd::SetConfig() function for both playback and record driver channels. The function has the following signature:

TInt DSoundScPdd::SetConfig(const TDesC8& aConfigBuf)
TInt DTemplateSoundScTxPdd::SetConfig(const TDesC8& aConfigBuf)
    {
    __KTRACE_SND(Kern::Printf(">DTemplateSoundScTxPdd::SetConfig"));
    
    // Read the new configuration from the LDD.
    TCurrentSoundFormatV02 config;
    TPtr8 ptr((TUint8*)&config,sizeof(config));
    Kern::InfoCopy(ptr,aConfigBuf);
    
    // TO DO: (mandatory)
    // Apply the specified audio configuration to the audio device.
    TInt r=KErrNone;
    
    __KTRACE_SND(Kern::Printf("<DTemplateSoundScTxPdd::SetConfig - %d",r));
    return(r);
    }

A configuration buffer in a packaged TCurrentSoundFormatV02 object containing the new configuration settings.

The PDD must read and locally save the contents of the TCurrentSoundFormatV02 configuration object that has been passed as a descriptor from the LDD. The template version contains the following code to achieve this:

// Read the new configuration from the LDD.
TCurrentSoundFormatV02 config;
TPtr8 ptr((TUint8*)&config, sizeof(config));
Kern::InfoCopy(ptr, aConfigBuf);

It is not necessary to check for configurations requested by the LDD that are not supported by the audio hardware device if the Caps() function has been implemented correctly by the PDD. This is because the LDD rejects such audio configuration requests from the client. However, if the PDD supports both playback and record driver channels then it needs to check that the specified configuration does not conflict with one already in use by the other channel.

The PDD sets up the audio hardware device according to the audio configuration specified if it is required. Some, if not all of this hardware configuration may be put off until data transfer is started, this is when DSoundScPdd::StartTransfer() is called, so for now the PDD needs to save the configuration.

If the PDD has to employ mono-to-stereo conversion of data using a conversion buffer while the audio configuration is set to mono, then the memory for the conversion buffer should be allocated at this time.

SetVolume()

Implement the DSoundScPdd::SetVolume function for both the playback and record driver channels. This has the following signature:

TInt DSoundScPdd::SetVolume(TInt aVolume)

The supplied implementation is as follows:

TInt DTemplateSoundScTxPdd::SetVolume(TInt aVolume)
    {
    __KTRACE_SND(Kern::Printf(">DTemplateSoundScTxPdd::SetVolume"));
    
    // TO DO: (mandatory)
    // Set the specified play volume on the audio device.
    TInt r=KErrNone;
    
    return(r);
    }

The PDD must first convert the volume/record level information specified into a form which can be used to program the hardware. This may require converting the value from a gain factor to an attenuation factor and/or applying a scaling factor. The LDD detects situations where the client specifies a record level /volume that is already in effect, and in this case does not unnecessarily call the PDD.

The PDD may opt to setup the audio hardware device within this function or it may defer this until data transfer is started with the function DSOundScPdd::StartTransfer(). For PDDs which support both record and playback driver channels, it is normal for audio devices to allow the record and playback gains to be programmed independently, this means that checking for conflicts between the channels is rarely required for this function.

StartTransfer()

Implement the DSoundScPdd::StartTransfer function for both the playback and record driver channels. This has the following signature:

TInt DSoundScPdd::StartTransfer()

The supplied implementation is as follows:

TInt DTemplateSoundScTxPdd::StartTransfer()
    {
    __KTRACE_SND(Kern::Printf(">DTemplateSoundScTxPdd::StartTransfer"));
    
    // TO DO: (mandatory)
    // Prepare the audio device for playback.
    TInt r=KErrNone;
    
    __KTRACE_SND(Kern::Printf("<DTemplateSoundScTxPdd::StartTransfer - %d",r));
    return(r);
    }

This function performs any configurations of the audio hardware device that were deferred from the DSoundScPdd::SetConfig() function. These configurations may include start-up of the DMA engine, enabling any interrupts related to audio transfer and interrupts that detect error conditions, for example. At this stage, no data has yet been supplied to the device for transfer as the function DSoundScPdd::TransferData() has not yet been called.

If the PDD supports both record and playback driver channels then it may be necessary to ensure that such hardware configuration on one channel cannot interfere with the other.

TransferData()

Implement the DSoundScPdd::TransferData function for both playback and record driver channels. This has the following signature:

TInt DSoundScPdd::TransferData(TUint aTransferID,TLinAddr aLinAddr,
                               TPhysAddr aPhysAddr,TInt aNumBytes)

Once transfer has been started by calling DSoundScPdd::StartTransfer(), the function TransferData is called repeatedly by the LDD for the transfer of each fragment. See the playback and record sections for details.

The template version for the record driver channel contains the following code:

TInt DTemplateSoundScRxPdd::TransferData(TUint aTransferID, TLinAddr aLinAddr,
                                         TPhysAddr /*aPhysAddr*/, TInt aNumBytes)
    {  
    TInt r=KErrNone;
   
    // Check that we can accept the request
    if (iPendingRecord>=KTemplateMaxRxDmaRequests)
        r=KErrNotReady;
    else
        {
        // Start a DMA transfer.
        iDmaRequest[iFlag]->iTransferID=aTransferID;
        iDmaRequest[iFlag]->iTransferSize=aNumBytes;
        // TO DO: (mandatory)
        // Supply the DMA source information.
        TUint32 src=0; // ???
        r=iDmaRequest[iFlag]->Fragment(src,aLinAddr,aNumBytes,KDmaMemDest|KDmaIncDest,0);
        if (r==KErrNone)
            {
            iDmaRequest[iFlag]->Queue();
            iPendingRecord++;
            if ((++iFlag)>=KTemplateMaxRxDmaRequests)
                iFlag=0;
         
            // TO DO: (mandatory)
            // Start the audio device transferring data.
            }
        }
                                    
    return(r);
    }
Note: The template version used by the playback function is very similar.

The first step is for the PDD to check that it has the capacity to accept a further transfer. For example, check that the PDD has a DMA request object that is free and that the DMA controller has the capacity for another transfer to be queued. If the PDD does not have the capacity to accept another transfer then it should immediately return KErrNotReady to signal this.

Otherwise, the PDD can start a DMA transfer. To do this it must acquire a DMA request object. The class DTemplateSoundScRxDmaRequest is the abstraction for a DMA record request and is defined as follows:

/** Wrapper function for a shared chunk sound driver record DMA request. */
class DTemplateSoundScRxDmaRequest : public DDmaRequest
    {
public:  
    DTemplateSoundScRxDmaRequest(TDmaChannel& aChannel, DTemplateSoundScRxPdd* aPdd,
                                 TInt aMaxTransferSize=0);
    static void DmaService(TResult aResult, TAny* aArg);
public:
    /** Pointer back to the PDD. */
    DTemplateSoundScRxPdd* iPdd;
    /** The transfer ID for this DMA request - supplied by the LDD. */
    TUint iTransferID;
    /** The transfer sizes in progress. */
    TUint iTransferSize;
    };
    This is derived from DDmaRequest the base class
    for a DMA request.

The data member iPdd is a pointer to the owning PDD object. This is used within the member function DmaService() which is the DMA callback function, and is called when the DMA request has been completed by the DMA framework. The data member iTransferID is a value supplied by the LDD which it uses to identify the transfer fragment. The PDD must save this value and pass it back to the LDD when the transfer is completed. Similarly, the member iTransferSize is used to hold the length of the transfer fragment in bytes. If the transfer is successful then this value is also passed back to the LDD to allow it to maintain its count of bytes recorded.

The record PDD class owns an array of DMA request objects:

/** The DMA request structures used for transfers. */             
DTemplateSoundScRxDmaRequest* iDmaRequest[KTemplateMaxRxDmaRequests];

It also owns the data member iFlag, which always holds the number for the next DMA request that should be used for transfer. Before starting the DMA transfer, setup the appropriate DMA request object with the transfer ID and the transfer length.

/** A flag selecting the next DMA request for transfer. */
TInt iFlag;

Next the function DDmaRequest::Fragment() is called to specify the details of the transfer to the DMA framework and to allow it to analyse this. Here the appropriate platform specific 32-bit DMA source identifier, src, needs to be supplied. The template driver shown previously specifies the destination memory address as a linear address but the physical address may be used instead. Both forms of this address are passed as arguments to the TransferData() function.

If fragmentation is successful then the request object is queued on the DMA channel DDmaRequest::Queue() and the value of iFlag is updated ready for the next transfer. The PDD class also keeps a count of the number of transfer fragments outstanding by incrementing the variable iPendingRecord:

/** The number of outstanding DMA record requests on the DMA channel. */   
TInt iPendingRecord;

The final part of this function requires platform specific code to start the audio hardware device transferring data. This needs to be executed for each fragment transferred or just for the first fragment queued following the StartTransfer() function.

Once the transfer is complete, either successfully or with an error, the DMA framework executes the static DMA callback function, DTemplateSoundScRxDmaRequest::DmaService(), as follows:

/**
DMA rx service routine. Called in the sound thread's DFC context by the s/w DMA controller.
@param aResult Status of DMA transfer.
@param aArg Argument passed to DMA controller.
*/ 
void DTemplateSoundScRxDmaRequest::DmaService(TResult aResult, TAny* aArg)
    {
    DTemplateSoundScRxDmaRequest& req=*(DTemplateSoundScRxDmaRequest*)aArg;
   
    TInt res=KErrNone;
    TInt bytesTransferred=req.iTransferSize;
    if (aResult!=DDmaRequest::EOk)
        {
        res=KErrCorrupt;
        bytesTransferred=0;
        }
      
    // Inform the LDD of the result of the transfer.
    req.iPdd->RecordCallback(req.iTransferID,res,bytesTransferred);   
    return;
    }

This function receives two arguments:

  • the result of the transfer from the DMA framework

  • an argument supplied to the DMA framework when the request object was created

In this specific case, the argument type is a pointer to the DMA request object, and allows the retrieval of the transfer ID and the transfer size. In the template driver version an unsuccessful transfer returns an error value of KErrCorrupt with a transfer byte count of zero.

From the callback function we call the record PDD function to decrement the count of transfer fragments outstanding and to inform the LDD of the completion of transfer.

void DTemplateSoundScRxPdd::RecordCallback(TUint aTransferID, TInt aTransferResult,TInt aBytesTransferred)
    {
    iPendingRecord--;
    Ldd()->RecordCallback(aTransferID,aTransferResult,aBytesTransferred);
    } 

StopTransfer()

Implement the DSoundScPdd::StopTransfer function for both the play and record driver channels. This has the following signature:

void DSoundScPdd::StopTransfer()

The PDD must reverse any operation performed on the audio hardware device done as part of StartTransfer() or TransferData(). This includes stopping the audio hardware device from transferring data and stopping the DMA channel.

The template version for the record driver channel contains the following code:

void DTemplateSoundScRxPdd::StopTransfer()
    {
    // Stop the DMA channel.
    iDmaChannel->CancelAll();
    iFlag=0;
    iPendingRecord=0;
    
    // TO DO: (mandatory)
    // Stop the audio device transferring data.
    }
Note: The version used by the playback function is very similar.

PauseTransfer()

Implement the DSoundScPdd::PauseTransfer() function for both the playback and record driver channels. This has the following signature:

TInt DSoundScPdd::PauseTransfer()

When pausing playback, there is normally some way to temporarily stop the codec and pause the DMA channel so that it can be resumed later starting from the next play sample.

Pausing record requires a different implementation. All active transfers must be aborted. When this has been achieved, the PDD must report back to the LDD how much data has been received for the fragment that was actively being transferred when the abort took place by calling DSoundScLdd::RecordCallBack(). If it is not possible to determine the byte count for the last fragment from the DMA controller, then you must find some other way to discover its value. One solution is to re-start a timer at the start of each record fragment, and use this to calculate how much data will have been written into the record buffer at the point the transfer is aborted.

Note: In this case the returned transfer ID is not important.

The supplied template implementation is as follows:

TInt DTemplateSoundScRxPdd::PauseTransfer()
    {    
    // Stop the DMA channel.
    iDmaChannel->CancelAll();
   
    if (iPendingRecord)
        {
        // TO DO: (mandatory)
        // Determine how much data was successfully transferred to the
        // record buffer before transfer was aborted.
        TInt byteCount=0; // ???
        Ldd()->RecordCallback(0,KErrNone,byteCount);
        iPendingRecord=0;
        }
    iFlag=0;
    
    // TO DO: (mandatory)
    // Halt recording on the audio device.
    TInt r=KErrNone;
    
    return(r); 
    }
Note: There is no need for the PDD to perform any state checking as this is already performed by the LDD. For example, checking that the device is not already paused or transferring data.

ResumeTransfer()

Implement the DSoundScPdd::ResumeTransfer function for both the playback and record driver channels. This has the following signature:

TInt DSoundScPdd::ResumeTransfer()

The template version for the record driver channel contains the following code:

TInt DTemplateSoundScTxPdd::ResumeTransfer()
    {
    __KTRACE_SND(Kern::Printf(">DTemplateSoundScTxPdd::ResumeTransfer"));
    
    // TO DO: (mandatory)
    // Resume playback on the audio device.
    TInt r=KErrNone;
            
    return(r);
    }

To resume playback, it is normally necessary to re-start the codec and resume the DMA channel in order to restart playback from the next play sample.

To resume record, all active transfers should have been aborted when the device was paused with the function PauseTransfer(). However, the LDD issues a new TransferData() request subsequent to this function to resume record data transfer so the only action required here is to recreate the same audio hardware setup that was achieved in response to the StartTransfer() function.

There is no need for the PDD to perform any state checking as this is already performed by the LDD. For example, checking that the device is not already paused.

PowerDown()

Implement the DSoundScPdd::PowerDown() function for both the playback and record driver channels. This has the following signature:

void DSoundScPdd::PowerDown()

The PDD must reverse any operation performed on the audio hardware as part of PowerUp().

CustomConfig()

Implement the DSoundScPdd::CustomConfig() function for both the playback and record driver channels. This has the following signature:

TInt DSoundScPdd::CustomConfig(TInt aFunction,TAny* aParam)

RSoundSc::CustomConfig() is called by the LDD in response to a custom configuration request by the client. Custom configurations allow clients to issue requests to setup platform specific audio configuration settings. Any such requests from the client with a function identifier equal to or above 0x10000000 are passed straight through to the PDD for function identification and implementation. These are handled in the context of the sound driver DFC thread.

If custom configuration is not supported, then the PDD should simply return KErrNotSupported.