At Sun, 15 Apr 2007 18:56:56 -0700 (PDT), Tim Harvey wrote:
At Wed, 11 Apr 2007 18:18:54 -0700 (PDT), Tim Harvey wrote:
Greetings,
I'm setting out to write an ALSA driver for a very limited device thats
behind
a PLD. The actual codec is a uda1380 and does 16bit MSB stereo
encode/decode.
The way the device is driven the sampling rate is limited to 8KHz and data transfer to/from the device I have to handle manually.
I've read over the 'writing an ALSA driver' tutorial and have started the driver but have run into a few questions:
- for the specs I've given above: 8000Hz sampling rate, 16bit stereo MSB
samples does the following snd_pcm_hardware_t look right?:
static snd_pcm_hardware_t my_playback_hw = { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID), .formats = SNDRV_PCM_FMTBIT_U16_BE, // is this 16bit MSB?
So, just to confirm, SNDRV_PCM_FMTBIT_U16_BE is appropriate for a 2byte sample where the first byte's msb is the msb of the sample and the 2nd bytes lsb is the lsb of the sample correct? And this will be the case regarding to the endianess of my system? (its an arm-be system).
Yes, U16_BE is 16bit big-endian format (but this is independent from the CPU endianess). This field specifies what the sound hardware supports.
.rates = SNDRV_PCM_RATE_8000, // seems redundant? .rate_min = 8000, .rate_max = 8000, .channels_min = 2, .channels_max = 2, .buffer_bytes_max = 32768, .period_bytes_min = 4096, .period_bytes_max = 32768, .periods_min = 1, .periods_max = 1024,
};
Looks fine to me. rates field shouldn't be empty, so it's not redundant (although it might work without it).
- I'm a little confused at how to deal with copying data from/to the
device.
It looks like I would implement the copy callback and copy the passed
buffer to
the device in the device specific manner I need to (playback), but I'm not clear where to put data that I receive from the device (capture). I notice that when the hw_params callback is called a dma buffer has been created by ALSA in the substream->runtime struct. I assume this is created based on
hte
buffer_bytes_max field above and I'm to copy data to this buffer? How do I know where to copy it within the buffer and what data ALSA has already
consumed
from the buffer?
It's a little bit complicated right now. If you don't need mmap mode transfer, the thing gets easier, but it restricts application usage.
to know if I need mmap mode that would depend on what userland apps I wish to support and if they use this or not correct? Are there many out there with this requirement?
For example, the software multi-playback via alsa-lib (dmix plugin) uses the mmap mode essentially. Also, you'll likely want to have an intermediate buffer in the case with small hardware buffer, and this will require a similar implementation like mmap mode.
Without mmap mode, you can implement copy and silence callbacks to copy the user-space data directly to the hardware buffer. In this case, the period and buffer sizes in snd_pcm_hardware have to be identical with what the hardware can accept. ISA GUS and emu8000_pcm are examples.
If my hardware buffer is somewhat small (ie ~1k) could I simply set snd_pcm_hardware to reflect this? Is there a minimum size for buffer_bytes_max/min before you would want to use an intermediate buffer?
There is no strict definition. But, 1k sounds indeed too small without an intermediate buffer.
Otherwise, you'd need an intermediate buffer. In this case, the transfer to the hardware is done either in the irq handler (via ack callback) or through a dedicated workqueue in background.
- I might need to create an intermediate buffer between ALSA's rather
large
buffers and my hardware buffer. Is there any driver that I could look at
that
does this? The tutorial talks about the vxpocket driver but that code
looks
very foriegn from the rest of the ALSA drivers.
The vx driver implementation is complex and not well suitable as a reference. If the manual copy can be done relatively fast, you can use helper functions in pcm_indirect.h, as in emu10k1/emupcm.c, rme32 and cs46xx. They transfer the data via ack callback, which is invoked from snd_pcm_period_elapsed().
Actually, this is a missing piece in the current ALSA design, and now becoming a frequently asked thing. Maybe it's good to think of a standard framework for non-DMA type driver implementation.
- I'm not sure I understand what the 'period' is about. Its clear to me
that
I need to call snd_pcm_period_elapsed() periodically from either an IRQ or other timed callback, but I'm not clear what the hardware pointer is or how that converts to 'frames' and 'periods'.
The hardware pointer (hw_ptr) is the current position of the transfer. Note that this is in frames unit. Meanwhile, the application pointer (appl_ptr) is the current position of the filled data by the app. Thus, appl_ptr - hw_ptr gives you the number of the rest data to be processed.
Both pointers are linear and _not_ the offset in a ring-buffer. It can be between 0 and runtime->boundary-1 (ca. LONG_MAX, aligned to period_size).
However, the pointer callback returns the offset in a ring-buffer, i.e. between 0 and (buffer_size-1). Then the pcm core layer recomputes the linear position.
The "frame" represents the unit, 1 frame = # channels x sample_bytes. In your case, 1 frame corresponds to 2 channels x 16 bits = 4 bytes.
The periods is the number of periods in a ring-buffer. In OSS, called as "fragments".
So,
- buffer_size = period_size * periods
- period_bytes = period_size * bytes_per_frame
- bytes_per_frame = channels * bytes_per_sample
I still don't understand what 'period_size' and a 'period' is?
The "period" defines the frequency to update the status, usually via the invokation of interrupts. The "period_size" defines the frame sizes corresponding to the "period time". This term corresponds to the "fragment size" on OSS. On major sound hardwares, a ring-buffer is divided to several parts and an irq is issued on each boundary. The period_size defines the size of this chunk.
On some hardwares, the irq is controlled on the basis of a timer. In this case, the period is defined as the timer frequency to invoke an irq.
I've also noticed that I seem to 'have' to call snd_pcm_lib_preallocate_pages_for_all or I get an error when ALSA tries to use the drive. I assume that I need to 'preallocate' or allocate manually upon each 'open' (or another callback?). Is this 'preallocate' going to allocate 1 buffer or 1 buffer per playback/capture?
The pre-allocation is done only once at driver initialziation time. It's pooled, and assigned at each snd_pcm_lib_malloc() call. If snd_pcm_lib_malloc() requires more memory than pre-allocated, it tries to allocate memories dynamically.
For capture would I grab this buffer pointer and copy data to it inside of a timer/irq event and adjust the hw_ptr manually?
It's similar like playback. In the case with a intermediate buffer, the data copy can be again done in ack callback. You don't have to care about the update of hw_ptr. It's a job of PCM middle layer. The lowlevel driver needs to provide proper callbacks. The pointer callback must return the current transfer-position offset. Then hw_ptr will be automatically updated according to this value.
Takashi