[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