linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Mike Brady <mikebrady@eircom.net>
To: tiwai@suse.de
Cc: Stefan Wahren <stefan.wahren@i2se.com>,
	devel@driverdev.osuosl.org, alsa-devel@alsa-project.org,
	f.fainelli@gmail.com, Eric Anholt <eric@anholt.net>,
	Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
	linux-kernel@vger.kernel.org, julia.lawall@lip6.fr,
	linux-rpi-kernel@lists.infradead.org,
	nishka.dasgupta_ug18@ashoka.edu.in,
	Kirill Marinushkin <k.marinushkin@gmail.com>,
	linux-arm-kernel@lists.infradead.org
Subject: [PATCH v2] ARM: staging: bcm2835-audio: interpolate audio delay
Date: Sun, 11 Nov 2018 18:21:29 +0000	[thread overview]
Message-ID: <B1CDD261-AB22-43CE-AF46-DFB5460B5D50@eircom.net> (raw)
Message-ID: <20181111182129.5m6dld8OkM9964XbFhrUWxPcaOAxQ4nus-CMWh5fD04@z> (raw)

When the BCM2835 audio output is used, userspace sees a jitter up to 10ms
in the audio position, aka "delay" -- the number of frames that must
be output before a new frame would be played.
Make this a bit nicer for userspace by interpolating the position
using the CPU clock.
The overhead is small -- an extra ktime_get() every time a GPU message
is sent -- and another call and a few calculations whenever the delay
is sought from userland.
At 48,000 frames per second, i.e. approximately 20 microseconds per
frame, it would take a clock inaccuracy of
20 microseconds in 10 milliseconds -- 2,000 parts per million --
to result in an inaccurate estimate, whereas
crystal- or resonator-based clocks typically have an
inaccuracy of 10s to 100s of parts per million.

Signed-off-by: Mike Brady <mikebrady@eircom.net>
---
Changes is v2:
- Add module parameter "enable_delay_interpolation"
- Modify update_audio_tstamp() to include interpolation.
---
 .../vc04_services/bcm2835-audio/bcm2835-pcm.c | 51 ++++++++++++++++++-
 .../vc04_services/bcm2835-audio/bcm2835.h     |  1 +
 sound/core/pcm_lib.c                          | 35 ++++++++++---
 3 files changed, 77 insertions(+), 10 deletions(-)

diff --git a/drivers/staging/vc04_services/bcm2835-audio/bcm2835-pcm.c b/drivers/staging/vc04_services/bcm2835-audio/bcm2835-pcm.c
index e66da11af5cf..7ca148e8094f 100644
--- a/drivers/staging/vc04_services/bcm2835-audio/bcm2835-pcm.c
+++ b/drivers/staging/vc04_services/bcm2835-audio/bcm2835-pcm.c
@@ -1,18 +1,26 @@
 // SPDX-License-Identifier: GPL-2.0
 /* Copyright 2011 Broadcom Corporation.  All rights reserved. */
 
+#include <linux/module.h>
+#include <linux/init.h>
 #include <linux/interrupt.h>
 #include <linux/slab.h>
-
 #include <sound/asoundef.h>
 
 #include "bcm2835.h"
 
+static bool enable_delay_interpolation = true;
+
+module_param(enable_delay_interpolation, bool, 0664);
+MODULE_PARM_DESC(enable_delay_interpolation,
+"Better delay reporting by interpolating between GPU notifications");
+
 /* hardware definition */
 static const struct snd_pcm_hardware snd_bcm2835_playback_hw = {
 	.info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
 		 SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
-		 SNDRV_PCM_INFO_DRAIN_TRIGGER | SNDRV_PCM_INFO_SYNC_APPLPTR),
+		 SNDRV_PCM_INFO_BATCH | SNDRV_PCM_INFO_DRAIN_TRIGGER |
+		 SNDRV_PCM_INFO_SYNC_APPLPTR),
 	.formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
 	.rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
 	.rate_min = 8000,
@@ -53,8 +61,11 @@ void bcm2835_playback_fifo(struct bcm2835_alsa_stream *alsa_stream,
 			   unsigned int bytes)
 {
 	struct snd_pcm_substream *substream = alsa_stream->substream;
+	struct snd_pcm_runtime *runtime = substream->runtime;
 	unsigned int pos;
 
+	runtime->delay = 0;
+
 	if (!alsa_stream->period_size)
 		return;
 
@@ -74,6 +85,18 @@ void bcm2835_playback_fifo(struct bcm2835_alsa_stream *alsa_stream,
 	atomic_set(&alsa_stream->pos, pos);
 
 	alsa_stream->period_offset += bytes;
+
+	/* Set interpolation_start_time to zero
+	 * whenever enable_delay_interpolation is false,
+	 * because enable_delay_interpolation could become
+	 * true at any time, and a non-zero interpolation_start_time
+	 * would then be taken as a legitimate starting time.
+	 */
+	if (enable_delay_interpolation)
+		alsa_stream->interpolation_start_time = ktime_get_ns();
+	else
+		alsa_stream->interpolation_start_time = 0;
+
 	if (alsa_stream->period_offset >= alsa_stream->period_size) {
 		alsa_stream->period_offset %= alsa_stream->period_size;
 		snd_pcm_period_elapsed(substream);
@@ -243,6 +266,7 @@ static int snd_bcm2835_pcm_prepare(struct snd_pcm_substream *substream)
 	atomic_set(&alsa_stream->pos, 0);
 	alsa_stream->period_offset = 0;
 	alsa_stream->draining = false;
+	alsa_stream->interpolation_start_time = 0;
 
 	return 0;
 }
@@ -293,6 +317,29 @@ snd_bcm2835_pcm_pointer(struct snd_pcm_substream *substream)
 	struct snd_pcm_runtime *runtime = substream->runtime;
 	struct bcm2835_alsa_stream *alsa_stream = runtime->private_data;
 
+	if (enable_delay_interpolation) {
+		/* Give userspace better delay reporting by
+		 * interpolating between GPU notifications.
+		 * But only if interpolation_start_time is not zero
+		 * and is before now.
+		 */
+
+		u64 now = ktime_get_ns();
+
+		if ((alsa_stream->interpolation_start_time) &&
+		    (alsa_stream->interpolation_start_time < now)) {
+			u64 interval = now -
+			  alsa_stream->interpolation_start_time;
+			u64 frames_output_in_interval =
+			  div_u64((interval * runtime->rate), 1000000000);
+			snd_pcm_sframes_t
+			  frames_output_in_interval_sized =
+			  frames_output_in_interval;
+			// the interpolation will always be zero or negative
+			runtime->delay = -frames_output_in_interval_sized;
+		}
+	}
+
 	return snd_pcm_indirect_playback_pointer(substream,
 		&alsa_stream->pcm_indirect,
 		atomic_read(&alsa_stream->pos));
diff --git a/drivers/staging/vc04_services/bcm2835-audio/bcm2835.h b/drivers/staging/vc04_services/bcm2835-audio/bcm2835.h
index e13435d1c205..39c582fc129d 100644
--- a/drivers/staging/vc04_services/bcm2835-audio/bcm2835.h
+++ b/drivers/staging/vc04_services/bcm2835-audio/bcm2835.h
@@ -78,6 +78,7 @@ struct bcm2835_alsa_stream {
 	unsigned int period_offset;
 	unsigned int buffer_size;
 	unsigned int period_size;
+	u64 interpolation_start_time;
 
 	struct bcm2835_audio_instance *instance;
 	int idx;
diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c
index 4e6110d778bd..574df7d7a1fa 100644
--- a/sound/core/pcm_lib.c
+++ b/sound/core/pcm_lib.c
@@ -229,19 +229,38 @@ static void update_audio_tstamp(struct snd_pcm_substream *substream,
 		(runtime->audio_tstamp_report.actual_type ==
 			SNDRV_PCM_AUDIO_TSTAMP_TYPE_DEFAULT)) {
 
-		/*
-		 * provide audio timestamp derived from pointer position
-		 * add delay only if requested
-		 */
+		// provide audio timestamp derived from pointer position
 
 		audio_frames = runtime->hw_ptr_wrap + runtime->status->hw_ptr;
 
-		if (runtime->audio_tstamp_config.report_delay) {
+		/*
+		 * If the runtime->delay is greater than zero, it's a
+		 * genuine delay, e.g. a delay due to a hardware fifo.
+		 *
+		 * But if the runtime->delay is less than zero, it's an
+		 * interpolated estimate of the number of frames output
+		 * since the hardware pointer was last updated.
+		 *
+		 * It would be calculated in the pointer callback.
+		 * For example, for the bcm_2835 driver, it is calculated in
+		 * snd_bcm2835_pcm_pointer().
+		 */
+
+		if (runtime->delay < 0) {
+			// The delay is an interpolated estimate...
 			if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
-				audio_frames -=  runtime->delay;
-			else
-				audio_frames +=  runtime->delay;
+				audio_frames += runtime->delay;
+		} else {
+			// The delay is a real delay. Add it if requested.
+			if (runtime->audio_tstamp_config.report_delay) {
+				if (substream->stream ==
+				    SNDRV_PCM_STREAM_PLAYBACK)
+					audio_frames -=  runtime->delay;
+				else
+					audio_frames +=  runtime->delay;
+			}
 		}
+
 		audio_nsecs = div_u64(audio_frames * 1000000000LL,
 				runtime->rate);
 		*audio_tstamp = ns_to_timespec(audio_nsecs);
-- 
2.17.1


         reply	other threads:[~2018-11-11 18:22 UTC|newest]

Thread overview: 23+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-10-18 10:57 [PATCH] staging: bcm2835-audio: interpolate audio delay Mike Brady
2018-10-18 11:02 ` Takashi Iwai
2018-10-18 19:48 ` Kirill Marinushkin
2018-10-22 19:08   ` Mike Brady
2018-10-22 19:17 ` [PATCH v2] " Mike Brady
2018-10-22 22:25   ` Kirill Marinushkin
2018-10-24  8:20     ` [alsa-devel] " Mike Brady
2018-10-24 18:06       ` Kirill Marinushkin
2018-10-24 19:54         ` Mike Brady
2018-10-24 22:02           ` Kirill Marinushkin
2018-10-25  7:37             ` Takashi Iwai
2018-10-25 17:20               ` Kirill Marinushkin
2018-10-28 14:24                 ` Mike Brady
2018-10-28 14:26               ` Mike Brady
2018-11-05 15:57                 ` Mike Brady
2018-11-05 16:11                   ` Takashi Iwai
2018-11-06 21:05                     ` Mike Brady
2018-11-06 21:31                       ` Takashi Iwai
2018-11-11 18:08                         ` Mike Brady [this message]
2018-11-11 18:21                           ` [PATCH v2] ARM: " Mike Brady
2018-11-11 20:18                           ` Stefan Wahren
2018-11-13 16:50                           ` Takashi Iwai
2019-01-01 10:02                             ` Mike Brady

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=B1CDD261-AB22-43CE-AF46-DFB5460B5D50@eircom.net \
    --to=mikebrady@eircom.net \
    --cc=alsa-devel@alsa-project.org \
    --cc=devel@driverdev.osuosl.org \
    --cc=eric@anholt.net \
    --cc=f.fainelli@gmail.com \
    --cc=gregkh@linuxfoundation.org \
    --cc=julia.lawall@lip6.fr \
    --cc=k.marinushkin@gmail.com \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-rpi-kernel@lists.infradead.org \
    --cc=nishka.dasgupta_ug18@ashoka.edu.in \
    --cc=stefan.wahren@i2se.com \
    --cc=tiwai@suse.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).