[alsa-devel] Questions about writing a new ALSA driver for a very limitted device
Takashi Iwai
tiwai at suse.de
Mon Apr 16 11:01:55 CEST 2007
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
More information about the Alsa-devel
mailing list