Record operation

Describes the operation of the Sound Driver for sound recording.

Client functions

Many aspects regarding the way that the Sound Driver recording operates are similar to the way it handles playback. One difference is how the memory location in the Shared Chunks for the transfer is determined. In play requests, the client specifies the source location in the chunk for the transfer having arranged for the data to be loaded there prior to the request. For record requests, the client just requests a buffers worth of record data and allows the driver to decide which buffer to use for the request.

The driver commences recording when the client issues the first RSoundSc::RecordData() request. However, unlike playback operation, once recording has commenced, the driver continues to record data into its available record buffers until it is told to stop. To stop the driver capturing record data, the client must either issue RSoundSc::Pause() to temporarily suspend recording, RSoundSc::CancelRecordData() to terminate record operation, or close the driver channel altogether.

The client specifies the number and size of the record buffers available to the driver within the shared chunk by calling either RSoundSc::SetBufferChunkCreate(), to create a buffer, or RSoundSc::SetBufferChunkOpen() to open an existing buffer.

When the driver starts recording, all the buffers in the shared chunk are empty, and the driver can use all of these available buffers. They are filled one by one, and if the client is slow to request the recorded data, then once the driver has filled all of the available empty buffers, it is forced to discard the earliest one filled and re-use this to continue recording data.

Each time the client requests a buffers worth of recorded data with RSoundSc::RecordData(), it is given the earliest one filled. This buffer is now said to be in-use and unavailable to the driver for capturing. This buffer remains in-use until it is freed by the client with a call of RSoundSc::ReleaseBuffer().

When buffers are in use by the client the number of buffers available to the driver for capture is reduced. If the client is slow to release buffers and the number of available buffers falls to two then further RSoundSc::RecordData() requests fail with KErrInUse until the client has freed some buffers. The driver always needs a working set of at least two buffers in order to achieve uninterrupted data capture. The driver always has a current buffer which is actively being filled and another queued in advance. If the client fails to take buffers at full speed then they are discarded by the driver.

Note: The driver does not slow down if it runs out of empty buffers.

Buffers

The driver maintains three buffer lists:

  • free list

  • completed list

  • in-use list.

A record buffer can only exist in one of these lists at any time. The free list contains buffers that are empty and not in use by the client. Once a buffer has been filled with record data it is moved into the completed buffer list. Here the buffer remains until it is passed back to the client in response to a record request. When a client is using the buffer it is deemed as in-use and is moved to the in-use list. Each time the client successfully calls RSoundSc::ReleaseBuffer() to free up a buffer then the driver moves this from the in-use list to the free list.

The driver also maintains two record buffers which are excluded from any of the three lists.

  • the current buffer, the one actively being filled with record data

  • the next buffer which becomes the active buffer once the current buffer is filled

During recording there may be DMA requests pending for both the current buffer and the next buffers.

Figure 1. The record buffer cycle .

The numbers one to five show the buffer cycle under normal operation, while the letters A to C show error induced operation.

When recording commences, the driver removes two buffers from the free list making one the current buffer and the other the next buffer (4 and 5).

When the current buffer is set as filled, the LDD normally adds this to the completed list (1). If a record error has occurred while recording to this buffer and it is only partially filled, or even empty then the buffer is still added to the completed list, as the client needs to be informed of the error (1). The only exception is in handling record pause, where a record buffer ends up being completed with no error and with no data added. In this case the buffer is added straight into the free list (A).

Having added the current buffer to one of these lists, the driver moves the next buffer to the current buffer (5) and then obtains a new next buffer (4). In normal operation this comes from the free list but if the client is slow, this list may be empty and the buffer is taken from the completed list (B). This is a buffer overflow situation which is reported to the client as a response to its next RSoundSc::RecordData() request as KErrOverFlow.

Whenever a buffer is filled, the driver checks if there is a record request pending (1). Similarly, when the driver processes a new record request it checks if a filled buffer is already available. In either case, if a request can be completed to the client then the earliest buffer completed is returned. If this buffer was filled successfully then it added to the in-use list (2). However, if an error occurred whilst filling the buffer then it is returned straight to the free list instead (C).

Each time the client successfully calls RSoundSc::ReleaseBuffer() to free up a buffer then the driver moves this from the in-use list to the free list (3).

Audio recording

RecordData()

If the driver is not already recording data then the first RSoundSc::RecordData() request is handled in the context of the driver DFC thread, as access to the audio hardware is required to enable record operation. However, for efficiency, subsequent record requests from the client are handled entirely in the context of the calling thread, as access to the audio hardware is not required to handle the request. The driver only has to check whether there is a filled record buffer already available. If there is then the driver completes the request straight away. If not, the driver saves the details of the request until a filled buffer does become available.

Returning to the case of a record request where the driver is not already in the process of recording data, the LDD first checks whether the client has specified or supplied a shared chunk to the driver channel and has set the audio configuration and record level. If the buffer configuration has not been specified then the driver cannot proceed and returns KErrNotReady. If the audio configuration or record level has not been specified then the LDD applies default settings to the audio hardware device for each instead by calling the functions DSoundSc::SetConfig() and DSoundScPdd::SetVolume() on the PDD.

StartTransfer()

Depending on the mapping attributes of the shared chunk, the LDD may now need to purge the region of the record chunk concerned. Next the LDD calls DSoundScPdd::StartTransfer() on the PDD to allow it to prepare the audio hardware device for record data transfer.

TransferData()

The LDD may need to break down the record buffer into memory fragments. These specify a physically contiguous region and are manageable by the PDD as a single transfer. The LDD queues as many transfer fragments of the current buffer on the PDD as it can accept with a call to DSoundScPdd::TransferData() for each fragment. If all fragments from the current buffer are accepted by the PDD then the LDD tries to queue fragments from the next buffer. As with playback, to support uninterrupted transfer of audio data the PDD must be able to accept multiple fragments simultaneously. As long as the LDD has transfer fragments still to queue, it continues to call DSoundScPdd::TransferData() until the PDD signals that it has temporarily reached its capacity by returning KErrNotReady.

RecordCallBack()

Each time the PDD completes the transfer of a fragment from a record buffer, it must signal this event back to the LDD by calling the function DSoundScLdd::RecordCallBack(). This must always be called in the context of the driver DFC thread. In executing DSoundScLdd::RecordCallback(), the LDD checks whether the entire transfer for the current buffer is now complete. If so, depending on the mapping attributes of the shared chunk, the LDD may need to purge the region of the record client. The LDD attempts to queue further fragments on the PDD, by calling DSoundScPdd::TransferData(), which should now have the capability to accept more transfers. So, the PDD should be written to handle calls to DSoundScPdd::TransferData() within its call back to DSoundScLdd::RecordCallback().

Pause and resume audio recording

The client can temporarily halt the progress of audio record at any time by issuing DSoundScLdd::Pause(). To configure the audio hardware device to halt recording, the LDD calls DSoundScPdd::PauseTransfer() on the PDD. This time, any active transfer should be aborted by the PDD. If a recording is halted the PDD must signal this event with a single call of the LDD function DSOundScLdd::RecordCallback(), which reports back any partial data already received. In this case, if transfer is resumed later, the LDD issues a new DSoundScPdd::TransferData() request to commence data transfer after calling DSoundScPdd::ResumeTransfer(). As access to the hardware is required in both cases, pause and resume are handled in the context of the driver DFC thread.

Error handling during recording

If the PDD reports an error when setting up the audio hardware device for recording then the LDD immediately completes the first record request back to the client returning the error value as the result. It will not restart record data transfer unless it receives a further record request from the client.

If the PDD reports an error when commencing the transfer of a record fragment or as the result of the transfer of a record fragment, then the LDD ceases transfer to that record buffer and instead reports the error back to the client. The error is returned in response to the record request which corresponds with that buffer.

Unexpected errors from the PDD are returned to the LDD via the functions DSoundScPdd::TransferData() and DSoundScLdd::RecordCallback().

The LDD does not try to cancel the transfer of other fragments for the same buffer that are already queued on the PDD, but it ignores their outcome. However, the LDD does try to carry on with the transfer to other available record buffers.