Skip site navigation (1)Skip section navigation (2)
Date:      Sun, 12 Dec 1999 13:06:57 +0900
From:      Seigo Tanimura <tanimura@r.dl.itc.u-tokyo.ac.jp>
To:        current@freebsd.org, gandalf@vilnya.demon.co.uk
Cc:        Seigo Tanimura <tanimura@r.dl.itc.u-tokyo.ac.jp>
Subject:   Re: The secondary buffer in pcm channel for DSPs with small DMA buffers
Message-ID:  <14419.8033.52078.72159A@silver.carrots.uucp.r.dl.itc.u-tokyo.ac.jp>
In-Reply-To: In your message of "Sat, 11 Dec 1999 18:55:38 %2B0900" <14418.8090.86742.72159A@silver.carrots.uucp.r.dl.itc.u-tokyo.ac.jp>
References:  <14418.8090.86742.72159A@silver.carrots.uucp.r.dl.itc.u-tokyo.ac.jp>

next in thread | previous in thread | raw e-mail | index | archive | help
--Multipart_Sun_Dec_12_13:06:56_1999-1
Content-Type: text/plain; charset=US-ASCII

On Sat, 11 Dec 1999 18:55:38 +0900,
  Seigo Tanimura <tanimura@r.dl.itc.u-tokyo.ac.jp> said:

Seigo> My CS4614 has got only 4KB of the DMA buffer, frequently failing to
Seigo> play pcm blocks continuously. The following patch adds the secondary
Seigo> pcm channel buffers with the size independent from that of a DSP. The
Seigo> patch works quite well with mpg123 and x11amp. For those who has the
Seigo> similar problem could you please try that?

A few fixes and updates have been added, including recovery from DMA
underflow. The value returned from chn_getptr() in chn_checkunderflow()
may get a lag when we get back to chn_write(), which is likely to
cause noise.

Consider a DMA buffer capable to hold 10 samples, as shown below.

	DMA buffer	0123456789
	Sample		----------
	Buffer Pointer	R---------
			F---------
	Playing Point	P---------

Since a DMA keeps running even after underflow occurs, P may advance,
leaving F.

	DMA buffer	0123456789
	Sample		----------
	Buffer Pointer	R---------
			F---------
	Playing Point	--P-------

If we fill the buffer with new samples in this situation, play begins
from somewhere in the middle of the samples. In the case shown below,
the sample c is the first one to be played. The sample a and b are
played after j, causing noise.

	DMA buffer	0123456789
	Sample		abcdefghij
	Buffer Pointer	R---------
			F---------
	Playing Point	--P-------  -->  cdefghijab

In order to avoid the lag, we can advance F upon underflow. A quarter
of the DMA buffer size would be fair.

	DMA buffer	0123456789
	Sample		--abcdefgh
	Buffer Pointer	R---------
			--F-------
	Playing Point	--P-------  -->  abcdefgh


With the following patch, x11amp and mpg123 play with no noise even
under an extremely heavy load of more than 50 :)



--Multipart_Sun_Dec_12_13:06:56_1999-1
Content-Type: text/plain; type=patch; charset=US-ASCII
Content-Disposition: attachment; filename="2ndbuffer.diff"
Content-Transfer-Encoding: 7bit

Index: channel.c
===================================================================
RCS file: /home/ncvs/src/sys/dev/sound/pcm/channel.c,v
retrieving revision 1.9
diff -u -r1.9 channel.c
--- channel.c	1999/12/05 19:09:12	1.9
+++ channel.c	1999/12/12 03:46:41
@@ -131,9 +131,8 @@
 }
 
 /*
- * chn_dmadone() updates pointers and wakes up any process sleeping
- * or waiting on a select().
- * Must be called at spltty().
+ * chn_dmadone() updates pointers and wakes up any process waiting
+ * on a select(). Must be called at spltty().
  */
 static void
 chn_dmadone(pcm_channel *c)
@@ -142,12 +141,25 @@
 
 	chn_dmaupdate(c);
 	if (ISA_DMA(b)) chn_isadmabounce(c); /* sync bounce buffer */
-	wakeup(b);
 	b->int_count++;
 	if (b->sel.si_pid && chn_polltrigger(c)) selwakeup(&b->sel);
 }
 
 /*
+ * chn_dmawakeup() wakes up any process sleeping. Separated from
+ * chn_dma_done() so that wakeup occurs only when feed from a
+ * secondary buffer to a DMA buffer takes place. Must be called
+ * at spltty().
+ */
+static void
+chn_dmawakeup(pcm_channel *c)
+{
+	snd_dbuf *b = &c->buffer;
+
+	wakeup(b);
+}
+
+/*
  * chn_dmaupdate() tracks the status of a dma transfer,
  * updating pointers. It must be called at spltty().
  *
@@ -181,8 +193,9 @@
 }
 
 /*
- * Check channel for underflow occured, reset DMA buffer in case of
- * underflow. It must be called at spltty().
+ * Check channel for underflow occured. Reset DMA buffer in case of
+ * underflow, so that new data can go into the buffer. It must be
+ * called at spltty().
  */
 static void
 chn_checkunderflow(pcm_channel *c)
@@ -191,9 +204,19 @@
 
 	if (b->underflow) {
 		DEB(printf("Clear underflow condition\n"));
-		b->rp = b->fp = chn_getptr(c);
-		b->rl = 0;
-		b->fl = b->bufsize;
+		/*
+		 * The DMA keeps running even after underflow occurs.
+		 * Hence the value returned by chn_getptr() here soon
+		 * gets a lag when we get back to chn_write(). Although
+		 * there are no easy and precise methods to figure out
+		 * the lag, a quarter of b->bufsize would be a fair
+		 * choice, provided that a DMA interrupt generates upon
+		 * each transfer of a half b->bufsize.
+		 */
+		b->rp = chn_getptr(c);
+		b->fp = (b->rp + b->bufsize / 4) % b->bufsize;
+		b->rl = b->bufsize / 4;
+		b->fl = b->bufsize - b->rl;
 	  	b->underflow=0;
 	} else {
 		chn_dmaupdate(c);
@@ -201,6 +224,70 @@
 }
 
 /*
+ * Feeds new data to the write dma buffer. Can be called in the bottom half.
+ * Hence must be called at spltty.
+ */
+static int
+chn_wrfeed(pcm_channel *c)
+{
+    	snd_dbuf *b = &c->buffer;
+    	snd_dbuf *bs = &c->buffer2nd;
+	int a, l, lacc;
+
+	/* ensure we always have a whole number of samples */
+	a = (1 << c->align) - 1;
+	lacc = 0;
+	/* Don't allow write unaligned data */
+	while (bs->rl > a && b->fl > a) {
+		/* ensure we always have a whole number of samples */
+		l = min(min(bs->rl, bs->bufsize - bs->rp), min(b->fl, b->bufsize - b->fp)) & ~a;
+		if (l == 0)
+			return lacc;
+		/* Move the samples, update the markers and pointers. */
+		bcopy(bs->buf + bs->rp, b->buf + b->fp, l);
+		bs->fl += l;
+		bs->rl -= l;
+		bs->rp = (bs->rp + l) % bs->bufsize;
+		b->rl += l;
+		b->fl -= l;
+		b->fp = (b->fp + l) % b->bufsize;
+		/* Accumulate the total bytes of the moved samples. */
+		lacc += l;
+	}
+
+	return lacc;
+}
+
+/* Feeds new data to the secondary write buffer. */
+static int
+chn_wrfeed2nd(pcm_channel *c, struct uio *buf)
+{
+    	snd_dbuf *bs = &c->buffer2nd;
+	int l, w, wacc;
+
+	/* ensure we always have a whole number of samples */
+	wacc = 0;
+	while (buf->uio_resid > 0 && bs->fl > 0) {
+		/*
+		 * The size of the data to move here does not have to be
+		 * aligned. We take care of it upon moving the data to a
+		 * DMA buffer.
+		 */
+		l = min(bs->fl, bs->bufsize - bs->fp);
+		/* Move the samples, update the markers and pointers. */
+		w = c->feeder->feed(c->feeder, c, bs->buf + bs->fp, l, buf);
+		if (w == 0) panic("no feed");
+		bs->rl += w;
+		bs->fl -= w;
+		bs->fp = (bs->fp + w) % bs->bufsize;
+		/* Accumulate the total bytes of the moved samples. */
+		wacc += w;
+	}
+
+	return wacc;
+}
+
+/*
  * Write interrupt routine. Can be called from other places (e.g.
  * to start a paused transfer), but with interrupts disabled.
  */
@@ -222,7 +309,18 @@
      	* needed when doing stereo and 16-bit.
      	*/
     	if (c->flags & CHN_F_MAPPED) start = c->flags & CHN_F_TRIGGERED;
-    	else start = (b->rl >= DMA_ALIGN_THRESHOLD && !(c->flags & CHN_F_ABORTING));
+    	else {
+		/*
+		 * Fill up the DMA buffer. This may result in making new
+		 * free space in the secondary buffer, thus we can wake up
+		 * the top half if feed occurs.
+		 */
+		if (chn_wrfeed(c) > 0) {
+			chn_dmawakeup(c);
+			while(chn_wrfeed(c) > 0);
+		}
+		start = (b->rl >= DMA_ALIGN_THRESHOLD && !(c->flags & CHN_F_ABORTING));
+	}
     	if (start) {
 		int l;
 		chn_dmaupdate(c);
@@ -237,13 +335,13 @@
 			/* Start DMA operation */
 	    		b->dl = c->blocksize ; /* record new transfer size */
 	    		chn_trigger(c, PCMTRIG_START);
-		} else if (b->dl != l) {
+		}
+		if (b->dl != l)
 			/*
 			 * we are near to underflow condition, so to prevent
-			 * audio 'clicks' clear next 1.5*dl bytes
+			 * audio 'clicks' clear next b->fl bytes
 			 */
-			 chn_clearbuf(c, (b->dl*3)/2);
-		}
+			 chn_clearbuf(c, b->fl);
     	} else {
 		/* cannot start a new dma transfer */
 		DEB(printf("cannot start wr-dma flags 0x%08x rp %d rl %d\n",
@@ -274,10 +372,9 @@
 int
 chn_write(pcm_channel *c, struct uio *buf)
 {
-	int 		a, l, w, timeout, ret = 0, rc;
+	int 		ret = 0, timeout;
 	long		s;
 	snd_dbuf       *b = &c->buffer;
-	int		threshold, maxthreshold, minthreshold;
 
 	if (c->flags & CHN_F_WRITING) {
 		/* This shouldn't happen and is actually silly
@@ -286,57 +383,39 @@
 	       	tsleep(&s, PZERO, "pcmwrW", hz);
 		return EBUSY;
 	}
-	a = (1 << c->align) - 1;
-	maxthreshold = (b->dl / 4 + a) & ~a;
-	minthreshold = a;
 	c->flags |= CHN_F_WRITING;
+	/*
+	 * Fill up the secondary and DMA buffer.
+	 * chn_wrfeed*() takes care of the alignment.
+	 */
 	s = spltty();
+	/* Check for underflow before writing into the buffers. */
 	chn_checkunderflow(c);
-	splx(s);
-	while ((buf->uio_resid + c->smegcnt) > minthreshold ) { /* Don't allow write unaligned data */
-		threshold = min((buf->uio_resid + c->smegcnt), maxthreshold);
-		if (b->fl < threshold) {
-			if (c->flags & CHN_F_NBIO) {
-				ret = -1;
-				break;
-			}
-			timeout = (buf->uio_resid >= b->dl)? hz : 1;
-			rc = tsleep(b, PRIBIO | PCATCH, "pcmwr", timeout);
-			if (rc == 0 || rc == EWOULDBLOCK) {
-				s = spltty();
-				chn_checkunderflow(c);
-				splx(s);
-				if (b->fl < minthreshold) continue; /* write only alligned chunk of data */
-			} else {
-#if 0
-			 	if (ret == EINTR) chn_abort(c);
-#endif
-				ret = rc;
-				break;
-			}
-		}
-		/* ensure we always have a whole number of samples */
-		l = min(b->fl, b->bufsize - b->fp) & ~a;
-		if (l == 0) continue;
-		w = c->feeder->feed(c->feeder, c, b->buf + b->fp, l, buf);
-		KASSERT(w, ("chn_write: no feed"));
-		s = spltty();
-		b->rl += w;
-		b->fl -= w;
-		b->fp = (b->fp + w) % b->bufsize;
-	      	splx(s);
-		DEB(if(1) printf("write %d bytes fp %d rl %d\n",w ,b->fp, b->rl));
-		if (!b->dl) chn_stintr(c);
-	}
-	if ((ret == 0) && (buf->uio_resid > 0)) {
-		s = spltty();
-		l = buf->uio_resid;
-		KASSERT( (c->smegcnt + l) < SMEGBUFSZ, ("resid overflow %d", l));
-		uiomove(c->smegbuf + c->smegcnt, l, buf);
-		c->smegcnt += l;
-		splx(s);
-	}
+  	while (chn_wrfeed2nd(c, buf) > 0 || chn_wrfeed(c) > 0);
+	/* Start playing if not yet. */
+	if (b->rl && !b->dl) chn_wrintr(c);
+   	if (!(c->flags & CHN_F_NBIO)) {
+   		/* Wait until all samples are played in blocking mode. */
+   		while (buf->uio_resid > 0) {
+			splx(s);
+			/* Wait for new free space to write new pcm samples. */
+			timeout = (buf->uio_resid >= b->dl)? hz / 20 : 1;
+   			ret = tsleep(b, PRIBIO | PCATCH, "pcmwr", timeout);
+   			s = spltty();
+#if notdef
+ 			if (ret == EINTR) chn_abort(c);
+#endif /* notdef */
+ 			if (ret == EINTR || ret == ERESTART) break;
+			/* Check for underflow before writing into the buffers. */
+			chn_checkunderflow(c);
+			/* Fill up the buffers with new pcm data. */
+  			while (chn_wrfeed2nd(c, buf) > 0 || chn_wrfeed(c) > 0);
+			/* Start playing if necessary. */
+  			if (b->rl && !b->dl) chn_wrintr(c);
+ 		}
+  	}
 	c->flags &= ~CHN_F_WRITING;
+   	splx(s);
 	return (ret > 0)? ret : 0;
 }
 
@@ -370,6 +449,65 @@
 
  */
 
+/*
+ * Feed new data from the read buffer. Can be called in the bottom half.
+ * Hence must be called at spltty.
+ */
+static int
+chn_rdfeed(pcm_channel *c)
+{
+    	snd_dbuf *b = &c->buffer;
+    	snd_dbuf *bs = &c->buffer2nd;
+	int l, lacc;
+
+	/* ensure we always have a whole number of samples */
+	lacc = 0;
+	while (bs->fl >= DMA_ALIGN_THRESHOLD && b->rl >= DMA_ALIGN_THRESHOLD) {
+		l = min(min(bs->fl, bs->bufsize - bs->fp), min(b->rl, b->bufsize - b->rp)) & DMA_ALIGN_MASK;
+		/* Move the samples, update the markers and pointers. */
+		bcopy(b->buf + b->rp, bs->buf + bs->fp, l);
+		bs->fl -= l;
+		bs->rl += l;
+		bs->fp = (bs->fp + l) % bs->bufsize;
+		b->rl -= l;
+		b->fl += l;
+		b->rp = (b->rp + l) % b->bufsize;
+		/* Accumulate the total bytes of the moved samples. */
+		lacc += l;
+	}
+
+	return lacc;
+}
+
+/* Feeds new data from the secondary read buffer. */
+static int
+chn_rdfeed2nd(pcm_channel *c, struct uio *buf)
+{
+    	snd_dbuf *bs = &c->buffer2nd;
+	int l, w, wacc;
+
+	/* ensure we always have a whole number of samples */
+	wacc = 0;
+	while (buf->uio_resid > 0 && bs->rl > 0) {
+		/*
+		 * The size of the data to move here does not have to be
+		 * aligned. We take care of it upon moving the data to a
+		 * DMA buffer.
+		 */
+		l = min(bs->rl, bs->bufsize - bs->rp);
+		/* Move the samples, update the markers and pointers. */
+		w = c->feeder->feed(c->feeder, c, bs->buf + bs->rp, l, buf);
+		if (w == 0) panic("no feed");
+		bs->fl += w;
+		bs->rl -= w;
+		bs->rp = (bs->rp + w) % bs->bufsize;
+		/* Accumulate the total bytes of the moved samples. */
+		wacc += w;
+	}
+
+	return wacc;
+}
+
 /* read interrupt routine. Must be called with interrupts blocked. */
 static void
 chn_rdintr(pcm_channel *c)
@@ -383,7 +521,18 @@
 		b->dl, b->rp, b->rl, b->fp, b->fl));
     	/* Restart if have enough free space to absorb overruns */
     	if (c->flags & CHN_F_MAPPED) start = c->flags & CHN_F_TRIGGERED;
-    	else start = (b->fl > 0x200 && !(c->flags & CHN_F_ABORTING));
+    	else {
+		/*
+		 * Suck up the DMA buffer. This may result in making new
+		 * captured data in the secondary buffer, thus we can wake
+		 * up the top half if feed occurs.
+		 */
+		if (chn_rdfeed(c) > 0) {
+			chn_dmawakeup(c);
+			while (chn_rdfeed(c) > 0);
+		}
+		start = (b->fl > 0x200 && !(c->flags & CHN_F_ABORTING));
+	}
     	if (start) {
 		int l = min(b->fl - 0x100, c->blocksize);
 		if (c->flags & CHN_F_MAPPED) l = c->blocksize;
@@ -431,7 +580,7 @@
 int
 chn_read(pcm_channel *c, struct uio *buf)
 {
-	int		w, l, timeout, limit, ret = 0;
+	int		ret = 0, timeout, limit;
 	long		s;
 	snd_dbuf       *b = &c->buffer;
 
@@ -441,33 +590,32 @@
 		return (EBUSY);
 	}
 
-	if (!b->rl & !b->dl) chn_stintr(c);
+  	s = spltty();
 	c->flags |= CHN_F_READING;
 	limit = buf->uio_resid - c->blocksize;
 	if (limit < 0) limit = 0;
-	while (buf->uio_resid > limit) {
-		s = spltty();
-		chn_dmaupdate(c);
-		splx(s);
-		if (b->rl < DMA_ALIGN_THRESHOLD) {
-			if (c->flags & CHN_F_NBIO) break;
-			timeout = (buf->uio_resid - limit >= b->dl)? hz : 1;
-			ret = tsleep(b, PRIBIO | PCATCH, "pcmrd", timeout);
+	/* Start capturing if not yet. */
+  	if (!b->rl & !b->dl) chn_rdintr(c);
+	/* Suck up the DMA and secondary buffers. */
+ 	while (chn_rdfeed(c) > 0 || chn_rdfeed2nd(c, buf) > 0);
+  	if (!(c->flags & CHN_F_NBIO)) {
+  		/* Wait until all samples are captured. */
+  		while (buf->uio_resid > 0) {
+			splx(s);
+			/* Wait for new pcm samples. */
+			timeout = (buf->uio_resid - limit >= b->dl)? hz / 20 : 1;
+  			ret = tsleep(b, PRIBIO | PCATCH, "pcmrd", timeout);
+  			s = spltty();
 			if (ret == EINTR) chn_abort(c);
 			if (ret == EINTR || ret == ERESTART) break;
-			ret = 0;
-			continue;
+			/* Start capturing if necessary. */
+ 			if (!b->rl & !b->dl) chn_rdintr(c);
+			/* Suck up the DMA and secondary buffers. */
+ 			while (chn_rdfeed(c) > 0 || chn_rdfeed2nd(c, buf) > 0);
 		}
-		/* ensure we always have a whole number of samples */
-		l = min(b->rl, b->bufsize - b->rp) & DMA_ALIGN_MASK;
-		w = c->feeder->feed(c->feeder, c, b->buf + b->rp, l, buf);
-		s = spltty();
-		b->rl -= w;
-		b->fl += w;
-		b->rp = (b->rp + w) % b->bufsize;
-	      	splx(s);
 	}
 	c->flags &= ~CHN_F_READING;
+  	splx(s);
 	return ret;
 }
 
@@ -534,6 +682,7 @@
 chn_resetbuf(pcm_channel *c)
 {
 	snd_dbuf *b = &c->buffer;
+	snd_dbuf *bs = &c->buffer2nd;
 
 	c->smegcnt = 0;
 	c->buffer.sample_size = 1;
@@ -547,6 +696,9 @@
 	b->prev_int_count = b->int_count = 0;
 	b->first_poll = 1;
 	b->underflow=0;
+	bs->rp = bs->fp = 0;
+	bs->dl = bs->rl = 0;
+	bs->fl = bs->bufsize;
 }
 
 void
@@ -631,6 +783,7 @@
     	long s;
     	int missing = 0;
     	snd_dbuf *b = &c->buffer;
+    	snd_dbuf *bs = &c->buffer2nd;
 
     	s = spltty();
     	if (b->dl) {
@@ -639,7 +792,7 @@
 		chn_trigger(c, PCMTRIG_ABORT);
 		chn_dmadone(c);
     	}
-    	missing = b->rl;
+    	missing = b->rl + bs->rl;
     	splx(s);
     	return missing;
 }
@@ -708,11 +861,18 @@
 int
 chn_init(pcm_channel *c, void *devinfo, int dir)
 {
+	snd_dbuf       *bs = &c->buffer2nd;
+
 	c->flags = 0;
 	c->feeder = &feeder_root;
 	c->buffer.chan = -1;
 	c->devinfo = c->init(devinfo, &c->buffer, c, dir);
 	chn_setdir(c, dir);
+	bs->bufsize = CHN_2NDBUFBLKSIZE * CHN_2NDBUFBLKNUM;
+	bs->buf = malloc(bs->bufsize, M_DEVBUF, M_NOWAIT);
+	bzero(bs->buf, bs->bufsize);
+	bs->rl = bs->rp = bs->fp = 0;
+	bs->fl = bs->bufsize;
 	return 0;
 }
 
Index: channel.h
===================================================================
RCS file: /home/ncvs/src/sys/dev/sound/pcm/channel.h,v
retrieving revision 1.3
diff -u -r1.3 channel.h
--- channel.h	1999/09/28 21:43:34	1.3
+++ channel.h	1999/12/12 03:46:41
@@ -79,3 +79,12 @@
 
 
 #define CHN_F_RESET		(CHN_F_BUSY)
+
+/*
+ * This should be large enough to hold all pcm data between
+ * tsleeps in chn_{read,write} at the highest sample rate.
+ * (which is usually 48kHz * 16bit * stereo = 192000 bytes/sec)
+ */
+#define CHN_2NDBUFBLKSIZE	(12 * 1024)
+/* The total number of blocks per secondary buffer. */
+#define CHN_2NDBUFBLKNUM	(3)
Index: datatypes.h
===================================================================
RCS file: /home/ncvs/src/sys/dev/sound/pcm/datatypes.h,v
retrieving revision 1.6
diff -u -r1.6 datatypes.h
--- datatypes.h	1999/12/12 02:18:57	1.6
+++ datatypes.h	1999/12/12 03:46:41
@@ -127,6 +127,7 @@
 	u_int8_t smegbuf[SMEGBUFSZ];
 	u_int32_t smegcnt;
 	void *devinfo;
+	snd_dbuf buffer2nd;
 };
 
 typedef void (pcm_swap_t)(void *data, int dir);
Index: dsp.c
===================================================================
RCS file: /home/ncvs/src/sys/dev/sound/pcm/dsp.c,v
retrieving revision 1.11
diff -u -r1.11 dsp.c
--- dsp.c	1999/12/12 02:18:57	1.11
+++ dsp.c	1999/12/12 03:46:41
@@ -325,7 +325,7 @@
 #define THE_REAL_SNDCTL_DSP_GETBLKSIZE _IOWR('P', 4, int)
     	case THE_REAL_SNDCTL_DSP_GETBLKSIZE:
     	case SNDCTL_DSP_GETBLKSIZE:
-		*arg_i = wrch? wrch->blocksize : 0; /* XXX rdch? */
+		*arg_i = CHN_2NDBUFBLKSIZE;
 		break ;
 
     	case SNDCTL_DSP_SETBLKSIZE:

-- 
Seigo Tanimura <tanimura@r.dl.itc.u-tokyo.ac.jp> <tanimura@freebsd.org>

--Multipart_Sun_Dec_12_13:06:56_1999-1--


To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-current" in the body of the message




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?14419.8033.52078.72159A>