[alsa-devel] Master Plan on rewinding

Alexander E. Patrakov patrakov at gmail.com
Sun Sep 7 17:16:48 CEST 2014


Hello.

(TL;DR: nothing really new except the strawman proposal about threads 
and the note about interaction of variable sample rate with rewindability)

As Takashi Iwai told me that the audio miniconference is for discussion 
only, and not for presentation of anything, I guess that I need to 
present the plans and options now. That's why this e-mail. The goal 
here, besides merely presenting the plan, is to identify points that 
everyone agrees upon, so that they are not discussed pointlessly at the 
miniconference. Also, this e-mail serves as a justification for the 
pending seemingly-destructive work.

First, the status quo. If anyone disagrees with the facts below, please 
complain loudly, before I make any conclusions!

1. PulseAudio does not call snd_pcm_rewindable(), because for some ALSA 
plugins it crashed. This crash is completely fixed in alsa-lib 1.0.28, 
but in some cases snd_pcm_rewindable() still returns wrong results.

2. PulseAudio blindly assumes that it can rewind up to hwbuf_frames - 
(snd_pcm_avail() + rewind_safeguard) frames. The rewind safeguard is 
needed due to reasons that I don't completely understand, but one of 
them is imprecise reporting of the hardware pointer, and another one is 
that the hardware transfers several bytes at a time, and the bytes we 
need to overwrite may be already cached by the hardware.

3. On the hw plugin, I could demonstrate two other bugs regarding 
snd_pcm_rewindable(): stale data and bogus negative return values.

4. There are ALSA plugins like "a52" or the "bluetooth" plugin from old 
version of bluez that return non-zero for snd_pcm_rewindable() and 
snd_pcm_rewind, but actually don't rewind, and where rewinding is almost 
impossible to implement (e.g. because this would seemingly involve 
unsending of already-sent bluetooth packets). See below why "almost", 
you need to search for "Alternative strawman proposal".

5. PulseAudio contains a workaround for non-rewindability of the a52 
plugin that also happens to apply to other ioplug-based plugins. 
However, this workaround does not match extplug-based plugins (including 
"dca"), and my patch extending the workaround has been rejected with the 
argument that this has to be fixed in ALSA.

6. The rate plugin has been made non-rewindable in alsa-lib 1.0.28 
because the old implementation was wrong and no simple fix exists.

7. Issues regarding rewindability of PulseAudio internals such as 
virtual sinks will be discussed separately.

And now the topics for discussion.

My immediate plan is to fix (3) and, if anyone replies to the rewind 
safeguard proposal, maybe start fixing (2) from the alsa-lib side.

For all proposals below, unless I say otherwise, I intend to write the 
required alsa-lib code myself if the proposal is accepted.

=== On the rewind safeguard ===

The consensus is that rewind_safeguard is a workaround for an ALSA bug. 
This information should come from snd_pcm_rewindable() from the hw 
plugin. I.e., on a hw device, it should not be equal to 
snd_pcm_mmap_hw_avail(). While the method of dealing with stale data and 
negative returns is obvious to me, I am not completely sure about the 
safeguard. Where should this value come from?

Proposal (credit goes to Raymond Yau): the safeguard should be ideally 
equal to the granularity of hardware pointer updates. However, we don't 
know this granularity a priori, and cannot know this because nobody will 
find out this information for old cards. Since we can't get this, let's 
instead use the upper bound: the minimum possible period size for the 
given sample rate, sample format and the number of channels.

On my desktop PC, on snd-hda-intel with analog outputs for S16LE stereo, 
the granularity is 32 bytes (= 8 samples), and I get the pointer 
granularity of 64 bytes (=16 samples) over HDMI. The minimum period size 
is 32 samples in both cases. Thus Raymond's method will overestimate the 
required safeguard on my cards, but it is still better than no safeguard 
at all, and less than the hard-coded value in PulseAudio.

On ymfpci, the pointer granularity is 5 ms, thus the value currently 
hard-coded in PA is insufficient. If I understand the card's limitations 
correctly, Raymond's method will yield the spot-on result for this card.

For the allegedly existing cards where the pointer granularity is always 
the same as the period size, Raymond's method will not work. However, I 
don't know any concrete example of such card (I didn't look at 
snd-firewire in detail, though). And in any case, my opinion is that 
this "pointer granularity = period size" limitation on such cards can be 
treated as a driver bug that needs to be fixed (by someone who knows the 
driver, not by me). On cards where pointer updates happen only on 
interrupts, the driver should not configure the card in such a way that 
one period visible to the userspace corresponds to one interrupt. 
Instead, it should always configure the card for the minimum possible 
period size, and report only part of the period interrupts to 
period_elapsed(). I.e.: userspace asked for 2 periods 64 ms each, but 
let's configure the card for, say, 64 periods 2 ms each, and use the 
"extra" interrupts only for updating the pointer.

Hopefully, the above heuristic and driver fix will also let PulseAudio 
get rid of special treatment of the "batch" cards, simply because they 
(in the "pointer granularity = period size" definition which is 
currently used in PulseAudio) will no longer exist.

If the heuristic (or something else) is accepted, then the question 
remains how to enable the use of snd_pcm_rewindable in PulseAudio. The 
problem is compatibility with older ALSA versions that, let me remind 
you, crash if snd_pcm_rewindable is called. I don't have proposals or 
opinions here. The obvious options are: compile-time alsa-lib version 
check with fallback to old code on buggy versions, run-time alsa-lib 
version check with the same fallback, add a (both compile-time and 
run-time) hard requirement of a fully-fixed alsa-lib version, or do 
nothing at all.

Possible discussion: alternative heuristics and, as mentioned, migration 
path in PulseAudio.

=== On non-rewindability of the rate plugin ===

I intend to write a rewindable resampler eventually, but don't have time 
now. I understand that it is an important task, but issues below (and 
the dayjob which you can change by offering me a new one) have higher 
priority for me. However, I want everyone to understand the following 
point now:

"The resampler has to be written from scratch for the reasons explained 
in http://permalink.gmane.org/gmane.linux.alsa.devel/122179 , and 
similar arguments apply to all other kinds of sound processing code that 
needs history."

For PulseAudio, it is also needed to figure out the desired interaction 
between variable rate and rewindability. Should rewinding other than 
"discard everything completely" be allowed at all on variable rate 
streams when the rewind crosses the sample rate change point? I.e., 
write 100 samples, change rate, write 50 samples, rewind 100 samples, 
what should be the resulting rate? Should we special-case small changes 
vs big ones?

Slightly off-topic, but still a discussion point.

=== On possibly-incomplete rewindability of the file plugin ===

The file plugin has quite hairy code involving the use of the write 
buffer. I have not verified its correctness or studied the code in 
detail. Anyway, it looks like it allows rewinding over that buffer only, 
even though its slave may allow rewinding further. If true, this would 
make the apparent rewindability useless, as the applications depend on 
being able to rewind almost everything, and won't like the 
apparently-random limitation (random because it depends on some obscure 
"wbuf" implementation detail).

I think that the plugin has to be changed to avoid this limitation if it 
really exists (which I still need to verify). Namely, I propose keeping 
a shadow copy of the slave's buffer, and, at the beginning of each API 
call, querying the slave about its hardware pointer. Only the part of 
the shadow buffer that corresponds to the distance covered by the 
slave's hardware pointer since the last call has to be written to the file.

The same applies to recording. As the code is much simpler there, it is 
obvious that the problem exists: if one rewinds in order to look at the 
past recorded samples again, the plugin will read further samples from 
the file instead of supplying the already-looked-at samples. The same 
solution with the slave hardware pointer should be applicable.

The possible discussion here amounts to suggesting alternative proposals.

=== On bogus rewindability of ladspa and extplug plugins ===

For ioplug, see below, because the situation is different enough.

Neither LADSPA nor snd_pcm_extplug_t have an API that allows to tell the 
corresponding plugin to forget the last N samples. We cannot fix LADSPA, 
as it is beyond our control. And we cannot really fix extplug, because 
this just means moving the problem down one level. External libraries 
(which are depended upon by the extplug-based plugins) are almost 
universally not rewindable, are beyond our control, and will not be made 
rewindable, because rewindability is hard and is not needed by anyone 
except ALSA and PulseAudio. Still, neither .rewindable not .rewind 
currently return 0.

So, I'd argue that the only correct result from snd_pcm_rewindable() is 
0 for ladspa and extplug plugins. However, right now, they forward the 
result from the slave. This needs to be fixed, and I have a patch.

With snd_pcm_rewind(), the situation is a bit different. It, obviously, 
does nothing to tell the LADSPA or extplug plugin that the rewind 
happened (because it can't). It also performs a rewind on the slave. So, 
assuming that the rewind succeeds and the DSP algorithm in the plugin 
uses N samples from the past, we'll end up with the output of the 
correct duration but with N corrupted samples. Not ideal. But if we 
don't perform the rewind at all and return 0, we'll end up with 
regressing PulseAudio: huge latency when applying software volume 
changes or starting new streams. By setting up the large buffer size, it 
essentially assumes that any rewind will succeed.

So, my hackish proposal is: make the .rewindable callbacks on ladspa and 
extplug return 0, keep the current implementation of the .rewind 
callback for compatibility with the current versions of PulseAudio, 
document this hack. I already have patch for this, will send out if we 
agree that the proposal is good.

An alternative proposal (that I would implement if the proposal above is 
rejected and someone actively confirms this one - but I don't like it) 
would be to keep the current versions of the .rewindable and .rewind 
callbacks and add new versions of them (and new user-visible ALSA API) 
for the situation when imperfect results are unacceptable.

A completely strawman proposal would be to introduce a low-latency 
thread, see below in the ioplug section.

Same for .forwardable and .forward.

Any other alternatives that I missed?

=== On bogus rewindability of some ioplug-based plugins ===

Ioplug-based plugins currently report rewindability according to the 
same "appl.ptr - hw.ptr" rule that is used by the hw plugin. However, 
this is incorrect.

In fact, there are two types of ioplug-based plugins. The jack plugin 
does not even have a .transfer callback, and does all the work of 
irrevocably submitting samples to the JACK server in a thread. So, it is 
almost completely rewindable (with that "almost" being equal to JACK 
buffer size). Proposal: keep this logic for all plugins without the 
.transfer callback. Expose the JACK buffer size as the minimum period 
size. Then the same rule proposed in the section about the hw plugin 
will work for .rewindable.

Other plugins do their irrevocable work in the .transfer callback and 
thus are not really rewindable. Proposal: return 0 from .rewindable and 
rewind ioplug callbacks for all plugins with a .transfer callback (but 
see the next section for an amendment). Document that the best practice 
is to have no .transfer callback.

Alternative strawman proposal (speculative): if a .transfer callback 
exists, call it from an alsa-lib-created thread (not from 
snd_pcm_writei!) at the last possible moment, using the least possible 
amount of data (that truly cannot be rewound) in the mmap-style buffer. 
The new mantra is: .transfer is the thing that moves the hardware 
pointer. This way, previously non-rewindable plugins could become 
rewindable. Not sure if this is possible without changing the ABI or 
possible at all, though - it essentially forces all plugins to declare 
mmap support. Also not sure what to do with the .pointer callback.

For the strawman proposal, the benefit is a possibility to have dmix on 
top of a52 (because freewheeling will now work), the downside is that 
all programs (not only those intending to do rewinds) now pay the cost 
of the background low-latency thread. Can we avoid this (e.g. with a new 
flag for snd_pcm_open), so that only programs that intend to do rewinds 
pay the price? Can this new flag apply to the batch hw drivers? Won't 
that inflate the test matrix for plugin code?

For discussion: do we implement the first proposal or try the strawman 
proposal? Any other suggestions regarding ioplug internals? Anyone else 
has a problem with two completely different interfaces (with and without 
.transfer) being exposed under the same ioplug framework?

=== On the pulse plugin ===

PulseAudio natively supports rewinding on the wire. However, the 
ioplug-based plugin doesn't. It pretends that it rewound something, but 
always passes 0, 0 as the last two parameters to pa_stream_write(). 
That's bad.

It looks possible (but I haven't checked) to figure out the correct 
arguments (i.e. to detect the rewinds done before the call to 
snd_pcm_writei) by looking at the application pointer. This way, it 
looks possible to support rewinds, but does not look possible to get the 
correct result (and not the result implied by the circular-buffer model) 
from the snd_pcm_rewindable() function.

Also, we apply proposals from the previous section, we'll either end up 
with a plugin that is truly non-rewindable and doesn't pretend to be 
rewindable, or (with a strawman proposal) a rewindable plugin that 
forces low latency on the PulseAudio side and causes a lot of wakeups. 
Both situations are suboptimal.

So, I see no way around adding the .rewind and .rewindable callbacks to 
snd_pcm_ioplug_callback, and treating the plugins with them as 
rewindable. I guess .forward and .forwardable should be added, too.

For discussion: do we do the above or declare that ioplug is not really 
a suitable infrastructure for the pulse plugin? What other alternatives 
can be proposed?

=== On communication of non-rewindability to the program ===

PulseAudio attempts to use timer-based scheduling and rewinds. It makes 
a big hardware buffer in expectation that it will be able to rewind - 
with the exception of a workaround for ioplug-based plugins. It should 
not set big latency for non-rewindable plugins, but currently has no 
idea how to get this bit of information. See 
http://lists.freedesktop.org/archives/pulseaudio-discuss/2014-April/020457.html 
for the initial problem statement.

If all of the proposals above are implemented, then we'll be at a point 
where each ALSA plugin is either almost fully rewindable, or not 
rewindable at all. So, contrary to the end of 
http://permalink.gmane.org/gmane.linux.alsa.devel/122191 , I think we 
need just snd_pcm_hw_params_is_seekable() (or rename it if you wish) 
with an essentially boolean result.

For discussion:

1. Decide, finally, on this bit of API, so that I can start working.

2. Decide what to do with old alsa-lib versions in PulseAudio. I.e. 
transition plan.

=== On the programmer expectations ===

(social issue)

Some people, including at least Andrew Eikum and Clemens Ladisch, at 
least once in the past expressed the opinion that amounts to "any plugin 
that does not allow random access is, as far as the ALSA API is 
concerned, buggy" (quoting 
http://permalink.gmane.org/gmane.linux.alsa.devel/122159 ), i.e. they 
are maybe asking for the impossible. We need to do something about that. 
Choices:

1. Document clearly that there exist non-rewindable plugins, and that it 
is not a bug until someone implements a working time machine.

2. Implement a strawman proposal with the background low-latency thread 
if it is doable (and I am not sure that it is doable).

3. Remove ioplug, extplug and ladspa as unfixable, fix the rate and 
adpcm plugins (or remove adpcm). Do something about pulse and jack 
because they rely on the to-be-removed ioplug infrastructure. Then all 
remaining plugins will indeed be fully rewindable.

4. Something else?

Sorry for the negative tone here, I know I am bad at formulating and 
resolving social issues.

=== Conclusion ===

When all the proposals are implemented, we'll have correct 
implementations of the rewind operation in all plugins (where "return 0" 
counts as correct), and "return 0" only where it is truly unavoidable. 
PulseAudio will be able to use all of that, and avoid doing rewinds on 
non-rewindable plugins. On this positive note, I'd like to finish 
writing this long e-mail.

-- 
Alexander E. Patrakov


More information about the Alsa-devel mailing list