[-,JACK,PCM,plugin] jack: Use boundary as hw_ptr wrap around
diff mbox

Message ID 1516794545-9868-1-git-send-email-twischer@de.adit-jv.com
State New
Headers show

Commit Message

Timo Wischer Jan. 24, 2018, 11:49 a.m. UTC
From: Timo Wischer <twischer@de.adit-jv.com>

instead of using buffer_size as wrap around.

This is required to detect Xruns.

It is also required to allow the JACK thread
to processes the whole ALSA audio buffer at once
without calling snd_pcm_avail_update() in between.

For example when the hw_ptr will be updated with
hw_ptr += buffer_size
and it is using the buffer_size as wrap around
hw_ptr %= buffer_size
would result in the same value as before the add operation.

Due to that the user application would not recognize
that the complete audio buffer was copied.

Signed-off-by: Timo Wischer <twischer@de.adit-jv.com>
---

This patch is depending on
http://mailman.alsa-project.org/pipermail/alsa-devel/2018-January/130942.html

Comments

Takashi Iwai Feb. 13, 2018, 5:40 a.m. UTC | #1
On Wed, 24 Jan 2018 12:49:05 +0100,
<twischer@de.adit-jv.com> wrote:
> 
> From: Timo Wischer <twischer@de.adit-jv.com>
> 
> instead of using buffer_size as wrap around.

No, the ioplug backend has to report the position from 0 to
buffer_size.

> This is required to detect Xruns.

The XRUN can be reported back from the backend just by returning an
error from pointer callback.

> It is also required to allow the JACK thread
> to processes the whole ALSA audio buffer at once
> without calling snd_pcm_avail_update() in between.
> 
> For example when the hw_ptr will be updated with
> hw_ptr += buffer_size
> and it is using the buffer_size as wrap around
> hw_ptr %= buffer_size
> would result in the same value as before the add operation.
> 
> Due to that the user application would not recognize
> that the complete audio buffer was copied.

This has nothing to do with the reported position.  If this happens,
it simply means an XRUN.  You should report the error instead.


thanks,

Takashi
Timo Wischer Feb. 13, 2018, 2:56 p.m. UTC | #2
Hello Takashi,

> This has nothing to do with the reported position.  If this happens,
it simply means an XRUN.  You should report the error instead.

When the ALSA buffer is full and the JACK daemon is requesting exactly the amount of samples of the buffer size
I do not see an under run here.
After such an operation the ALSA buffer is empty
but the JACK daemon has not read more samples than available.

Exactly in this case we would increment the hw_ptr += buffer_size
but this would not be recognized by the ALSA library
when we are using a wrap around of  buffer_size.


> No, the ioplug backend has to report the position from 0 to buffer_size.

I know but I think the ioplug API implementation has possibly to be changed to allow exactly such use cases
as described above.

Or do you have another idea how to report such a hw_ptr change?

Best regards

Timo Wischer

Advanced Driver Information Technology GmbH
Engineering Software Base (ADITG/ESB)
Robert-Bosch-Str. 200
31139 Hildesheim
Germany

Tel. +49 5121 49 6938
Fax +49 5121 49 6999
twischer@de.adit-jv.com

ADIT is a joint venture company of Robert Bosch GmbH/Robert Bosch Car Multimedia GmbH and DENSO Corporation
Sitz: Hildesheim, Registergericht: Amtsgericht Hildesheim HRB 3438
Geschäftsführung: Wilhelm Grabow, Ken Yaguchi
Takashi Iwai Feb. 13, 2018, 5:03 p.m. UTC | #3
On Tue, 13 Feb 2018 15:56:08 +0100,
Wischer, Timo (ADITG/ESB) wrote:
> 
> Hello Takashi,
> 
> > This has nothing to do with the reported position.  If this happens,
> it simply means an XRUN.  You should report the error instead.
> 
> When the ALSA buffer is full and the JACK daemon is requesting exactly the amount of samples of the buffer size
> I do not see an under run here.
> After such an operation the ALSA buffer is empty
> but the JACK daemon has not read more samples than available.

The backend has nothing to do with such a case.  This happens only
when the application uses periods=1, and then it implies that the
application must know of the situation.

The ioplug simulates the say the hardware device would behave.  It
notifies the period elapse via file-descriptor and it reports the DMA
ring-buffer position.  The ring buffer is between 0 to buffer size,
you must not exceed it in the reporting.


Takashi

> Exactly in this case we would increment the hw_ptr += buffer_size
> but this would not be recognized by the ALSA library
> when we are using a wrap around of  buffer_size.
> 
> 
> > No, the ioplug backend has to report the position from 0 to buffer_size.
> 
> I know but I think the ioplug API implementation has possibly to be changed to allow exactly such use cases
> as described above.
> 
> Or do you have another idea how to report such a hw_ptr change?
> 
> Best regards
> 
> Timo Wischer
> 
> Advanced Driver Information Technology GmbH
> Engineering Software Base (ADITG/ESB)
> Robert-Bosch-Str. 200
> 31139 Hildesheim
> Germany
> 
> Tel. +49 5121 49 6938
> Fax +49 5121 49 6999
> twischer@de.adit-jv.com
> 
> ADIT is a joint venture company of Robert Bosch GmbH/Robert Bosch Car Multimedia GmbH and DENSO Corporation
> Sitz: Hildesheim, Registergericht: Amtsgericht Hildesheim HRB 3438
> Geschäftsführung: Wilhelm Grabow, Ken Yaguchi
>
Timo Wischer Feb. 15, 2018, 10:36 a.m. UTC | #4
Hello Takashi,

> This happens only when the application uses periods=1,

It can also happen with 2 periods per buffer e.g. when the user application is too late.
I will try to illustrate the use case with the following time line:

Time line: 0--R1-W1-R2--R3-W2-W3-...

0 buffer is full (2 periods)
R1 JACK daemon reads 1 period (1 period left in the buffer)
W1 User application writes 1 period (2 periods in the buffer)
R2 JACK daemon reads 1 period (1 period left in the buffer)
R3 JACK daemon reads 1 period (buffer empty)
But it is not yet an under run because JACK has not yet read invalid data.
Due to the blocked user application (e.g low scheduling priority) the pointer() callback was not called before the second read.
In this case the next pointer() call has to result in a delta of 2 periods.
But this is not possible if pointer() is not allowed to return >=buffer_size.

W2 User application writes 1 period (1 periods in the buffer)
W3 User application writes 1 period (2 periods in the buffer)
Continue with 0

> It notifies the period elapse via file-descriptor
But this notification will be processed later if the user application is blocked at the moment (e.g. by higher priority tasks).

Do you also see my problem, now?

Best regards

Timo Wischer

Advanced Driver Information Technology GmbH
Engineering Software Base (ADITG/ESB)
Robert-Bosch-Str. 200
31139 Hildesheim
Germany

Tel. +49 5121 49 6938
Fax +49 5121 49 6999
twischer@de.adit-jv.com

ADIT is a joint venture company of Robert Bosch GmbH/Robert Bosch Car Multimedia GmbH and DENSO Corporation
Sitz: Hildesheim, Registergericht: Amtsgericht Hildesheim HRB 3438
Geschäftsführung: Wilhelm Grabow, Ken Yaguchi
Takashi Iwai Feb. 15, 2018, 11:32 a.m. UTC | #5
On Thu, 15 Feb 2018 11:36:21 +0100,
Wischer, Timo (ADITG/ESB) wrote:
> 
> Hello Takashi,
> 
> > This happens only when the application uses periods=1,
> 
> It can also happen with 2 periods per buffer e.g. when the user application is too late.
> I will try to illustrate the use case with the following time line:
> 
> Time line: 0--R1-W1-R2--R3-W2-W3-...
> 
> 0 buffer is full (2 periods)
> R1 JACK daemon reads 1 period (1 period left in the buffer)
> W1 User application writes 1 period (2 periods in the buffer)
> R2 JACK daemon reads 1 period (1 period left in the buffer)
> R3 JACK daemon reads 1 period (buffer empty)
> But it is not yet an under run because JACK has not yet read invalid data.
> Due to the blocked user application (e.g low scheduling priority) the pointer() callback was not called before the second read.
> In this case the next pointer() call has to result in a delta of 2 periods.
> But this is not possible if pointer() is not allowed to return >=buffer_size.
> 
> W2 User application writes 1 period (1 periods in the buffer)
> W3 User application writes 1 period (2 periods in the buffer)
> Continue with 0
> 
> > It notifies the period elapse via file-descriptor
> But this notification will be processed later if the user application is blocked at the moment (e.g. by higher priority tasks).
> 
> Do you also see my problem, now?

I know that's the problem, but you're scratching in the wrong surface.
This isn't the issue to be fixed in the backend side.  The fact that
the pointer callback returns 0 to buffer_size-1 is *the* design.  You
can't ignore that.

If you need to improve such a situation, you'd have to fix in the
ioplug implementation itself, not the jack plugin.


Takashi

Patch
diff mbox

diff --git a/jack/pcm_jack.c b/jack/pcm_jack.c
index 3aed332..c22a5d0 100644
--- a/jack/pcm_jack.c
+++ b/jack/pcm_jack.c
@@ -40,6 +40,7 @@  typedef struct {
 
 	char **port_names;
 	unsigned int num_ports;
+	snd_pcm_uframes_t boundary;
 	unsigned int hw_ptr;
 	unsigned int sample_bits;
 	snd_pcm_uframes_t min_avail;
@@ -130,6 +131,21 @@  static int snd_pcm_jack_poll_revents(snd_pcm_ioplug_t *io,
 static snd_pcm_sframes_t snd_pcm_jack_pointer(snd_pcm_ioplug_t *io)
 {
 	snd_pcm_jack_t *jack = io->private_data;
+
+	/* ALSA library is calulating the delta between the last pointer and
+	 * the current one.
+	 * Normally it is expecting a value between 0 and buffer_size.
+	 * The following example would result in an negative delta
+	 * which would result in a hw_ptr which will be reduced.
+	 *  last_hw = jack->boundary - io->buffer_size
+	 *  hw = 0
+	 * But we cannot use
+	 * return jack->hw_ptr % io->buffer_size;
+	 * because in this case an update of
+	 * hw_ptr += io->buffer_size
+	 * would not be recognized by the ALSA library.
+	 * Therefore we are using jack->boundary as the wrap around.
+	 */
 	return jack->hw_ptr;
 }
 
@@ -162,7 +178,7 @@  snd_pcm_jack_process_cb(jack_nframes_t nframes, snd_pcm_ioplug_t *io)
 
 	while (xfer < nframes) {
 		snd_pcm_uframes_t frames = nframes - xfer;
-		snd_pcm_uframes_t offset = hw_ptr;
+		snd_pcm_uframes_t offset = hw_ptr % io->buffer_size;
 		snd_pcm_uframes_t cont = io->buffer_size - offset;
 
 		if (cont < frames)
@@ -176,7 +192,8 @@  snd_pcm_jack_process_cb(jack_nframes_t nframes, snd_pcm_ioplug_t *io)
 		}
 		
 		hw_ptr += frames;
-		hw_ptr %= io->buffer_size;
+		if (hw_ptr >= jack->boundary)
+			hw_ptr -= jack->boundary;
 		xfer += frames;
 	}
 	jack->hw_ptr = hw_ptr;
@@ -200,6 +217,8 @@  static int snd_pcm_jack_prepare(snd_pcm_ioplug_t *io)
 	err = snd_pcm_sw_params_current(io->pcm, swparams);
 	if (err == 0) {
 		snd_pcm_sw_params_get_avail_min(swparams, &jack->min_avail);
+		/* get boundary for available calulation */
+		snd_pcm_sw_params_get_boundary(swparams, &jack->boundary);
 	}
 
 	/* deactivate jack connections if this is XRUN recovery */