Re: Driver design question

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



At Mon, 25 Sep 2006 15:54:22 -0400,
Lee Revell wrote:
> 
> On Tue, 2006-09-19 at 17:15 +0200, Takashi Iwai wrote:
> > The workq task is a pseudo-DMA engine that runs in background.
> > When it's woken up, it feeds data from intermediate buffer to hardware
> > as much as possible.  The available data can be found in
> > 
> > If all data are fed, it sleeps again (or exit
> > the task).  The timer wakes up or schedule the new workq task.
> > 
> 
> I've attached our code, which actually uses a combination of the two
> (copy callback and an intermediate buffer).  It's certainly not the most
> efficient implementation but it works, at least with aplay.  However if
> we play an MP3 using madplay, the sound is OK for the first minute or so
> after which ticks or glitches are heard.  It sounds as if old parts of
> the buffer are being replayed.
> 
> The difference in the applications seems to be that madplay writes in
> chunks of 0x480 bytes (IOW, arbitrary) while aplay always uses 0x4000
> (which matches the hardware buffer size).
> 
> Here is the code - can you see anything wrong with it?  It's derived
> from the ALSA dummy driver and still uses the dummy driver's hardware
> pointer and timer scheme for calling pcm_period_elapsed() - could this
> be the problem?  My attempts to track the hardware pointer more
> accurately and call pcm_period_elapsed from the copy callback have not
> been successful.
> 
> Most of the work is done in the workqueue dream_handle_pcm_queue() and
> the copy callback.

Well, when you have an intermediate buffer (allocated via
snd_pcm_lib_malloc(), you don't need copy and silent callbacks.  The
data is written on the intermediate buffer.  Then, the workq copies
the data again from this buffer to the hardware in the background.

As I mentioned, the helpers in pcm-indirect.h might make things eaiser
for such an implementation.  In your case, a pseudo code would look
like below.


...
#include <sound/pcm-indirect.h>
...

struct dream {
	...
	unsigned int playback_hw_ptr; /* current position */
	struct snd_pcm_indirect playback_rec;
	struct work_struct playback_work;
	...
};

/* transfer the given size of data to the hardware */
static void dream_playback_copy(struct snd_pcm_substream *substream,
				struct snd_pcm_indirect *rec, size_t bytes)
{
	struct dream *chip = snd_pcm_substream_chip(substream);
	unsigned char *src = substream->runtime->dma_area + rec->sw_data;

	while (bytes--)
		do_write_data(chip, *src++);
}

/* work to transfer the data for playback */
static void dream_work_transfer(void *data)
{
	struct dream *chip = data;
	struct snd_pcm_substream *substream = chip->playback_substream;
	if (!substream)
		return;
	snd_pcm_indirect_playback_transfer(substream,
		&chip->playback_rec, dream_playback_copy);
}

/* ack callback - just schedule work */
static int dream_playback_ack(struct snd_pcm_substream *substream)
{
	struct dream *chip = snd_pcm_substream_chip(substream);
	schedule_work(&chip->playback_work);
	return 0;
}

/* cancel work */
static void dream_playback_cancel(struct snd_pcm_substream *substream)
{
	cancel_delayed_work(&chip->pb_work);
}

/* prepare callback -
 * initialize pcm_indirect record, too
 */
static int dream_playback_prepare(struct snd_pcm_substream *substream)
{
	struct dream *chip = snd_pcm_substream_chip(substream);

	memset(&chip->playback_rec, 0, sizeof(chip->playback_rec));
	chip->playback_rec.hw_buffer_size = HARDWARE_BUFFER_SIZE; /* byte size */
	chip->playback_rec.sw_buffer_size = snd_pcm_lib_buffer_bytes(substream);
	chip->playback_hw_ptr = 0;
	....
	return 0;
}

/* trigger callback */
static int dream_playback_trigger(struct snd_pcm_substream *substream, int cmd)
{
	struct dream *chip = snd_pcm_substream_chip(substream);

	switch (cmd) {
	case SNDRV_PCM_TRIGGER_START:
		dream_playback_ack(substream); /* fill the data */
		start_timer();
		break;
	case SNDRV_PCM_TRIGGER_STOP:
		stop_timer();
		dream_playback_cancel(substream);
		break;
	default:
		return -EINVAL;
	}
	return 0;
}

/* pointer callback -
 * playback_hw_ptr is the current byte position in the ring buffer,
 * which is updated in the timer handler
*/
static snd_pcm_uframes_t dream_playback_pointer(struct snd_pcm_substream *substream)
{
	struct dream *chip = snd_pcm_substream_chip(substream);

	return snd_pcm_indirect_playback_pointer(substream,
		&chip->playback_rec, chip->playback_hw_ptr);
}

/* open, close, hw_params and hw_free are as usual */

static struct snd_pcm_ops dream_ops = {
	.open = dream_playback_open,
	.close = dream_playback_close,
	.ioctl = snd_pcm_lib_ioctl,
	.hw_params = dream_playback_hw_params,
	.hw_free = dream_playback_hw_free,
	.prepare = dream_playback_prepare,
	.pointer = dream_playback_pointer,
	.trigger = dream_playback_trigger,
	.pointer = dream_playback_pointer,
	.ack = dream_playback_ack,
};


If the data transfer doesn't take much time, you can implement without
workqueue but calling dream_playback_copy() from the ack callback
directly.


Takashi

-------------------------------------------------------------------------
Take Surveys. Earn Cash. Influence the Future of IT
Join SourceForge.net's Techsay panel and you'll get the chance to share your
opinions on IT & business topics through brief surveys -- and earn cash
http://www.techsay.com/default.php?page=join.php&p=sourceforge&CID=DEVDEV
_______________________________________________
Alsa-devel mailing list
Alsa-devel@xxxxxxxxxxxxxxxxxxxxx
https://lists.sourceforge.net/lists/listinfo/alsa-devel

[Index of Archives]     [ALSA User]     [Linux Audio Users]     [Kernel Archive]     [Asterisk PBX]     [Photo Sharing]     [Linux Sound]     [Video 4 Linux]     [Gimp]     [Yosemite News]

  Powered by Linux