[alsa-devel] On non-rewindability of resamplers (was: Testing rewindability of a pcm)

Alexander E. Patrakov patrakov at gmail.com
Thu Apr 24 23:01:22 CEST 2014


24.04.2014 18:00, Clemens Ladisch wrote:
> Alexander E. Patrakov wrote:
>> And we don't even have a rewindable resampler implementation!
>
> The rate plugin implements rewinding.  (I haven't tested if this
> implementation is correct.  What is the problem?)

I was going to demonstrate a program that produces incorrect output, but 
instead got a program that crashes. Still, on extplug with upmix, it 
does demonstrate essentially the same issue with internal state nicely, 
search for "=====" below. And the speculation text below is the one 
about incorrect output that I wrote after looking at the pcm_rate.c code 
but which is not testable right now. So much for the failed trolling 
attempt :(

<speculation>
The implementation of rewinds in the rate plugin is indeed incorrect. I 
will explain this now and show the bigger problem then (after a "+++++").

When resampling, each of the input samples affects more that one output 
sample, and each output sample is affected by more than one input 
sample. In the windowed-sinc approach to resampling (which is 
implemented by speex, libsamplerate and ffmpeg), this is determined by 
the width of the window, which is typically several (8..100) samples and 
depends on the rate ratio and resampler quality setting.

As a consequence, each output fragment (received from 
rate->ops.convert_s16 or from rate->ops.convert in do_convert() into 
dst_areas) is affected not only by the samples in the input fragment 
(src_areas), but also by the samples in the previous input fragment. 
I.e. by a few samples that are logically just before src_offset. Each 
sample rate converter, however, keeps a copy of them in its own history 
buffer (which is called "magic_samples" in speex), instead of looking 
before src_offset.

The important part of the above is that a sample rate converter is not 
stateless, and that this state left over from the previous input can 
yield non-silent output fragment as a result of conversion of a fragment 
of silence.

Let's also note an implicit precondition that do_convert is based on. 
The precondition is that the internal state of the resampler (i.e 
rate->obj) should match what was there after converting the previous 
samples (i.e. those that are just before src_offset now). Without 
rewinds, this precondition is trivially satisfied, as nobody touches 
rate->obj between converting the previous fragment and this fragment.

With rewinds, this precondition is violated: consider a program that 
uses 4 periods, prefills them with silence, and then, in a loop, rewinds 
a period, writes a period of silence, and writes a period of square 
waves. For your convenience, this program is attached. To test the rate 
plugin, edit it in order to use the correct device (such as plughw:2) 
and a rate (such as 32000 Hz) that the card doesn't support. As it 
always overwrites square waves with silence before the hardware pointer 
goes over them, the only correct output from this program on any device 
that properly supports rewinds is pure silence (unless the program 
complains about anything).

If I understand the code correctly (which, given the crash, is false), 
the rate plugin sometimes would give the square wave to the resampler as 
an input. And it never tells the resampler to forget that - there is no 
such op in snd_pcm_rate_ops_t! In fact, the rewind path 
(snd_pcm_rate_rewind, snd_pcm_rate_move_applptr) calls only read-only 
functions on obj, which is what I object to. However, see below why my 
requirements (which are indeed valid requirements if the rate plugin 
must implement rewinds correctly) are actually unreasonable.
</speculation>

Now to the crash that was totally unexpected. The problem is that, in 
snd_pcm_rate_fast_ops, the .rewindable callback gets defaulted to NULL 
and that got dereferenced through the snd_pcm_rewindable() call in my 
test program. OK, we can comment out the call to snd_pcm_rewindable() 
and the check. But then, with alsa-lib 1.0.27.2, snd_pcm_rewind() would 
return negative numbers close to minus buffer size! I do see a commit by 
Andrew Eikum in the recent git history, but that only papers over the 
problem, as it was never explained where these negative numbers came from.

Installation of alsa-lib from git yields snd_pcm_rewind() that forgets 
~4090 samples out of 1024 requested. Also not usable. And worse, the 
program produces a lot of steady sound - so here is obviously a bug with 
accounting in addition to the fundamental problem with state that I 
speculated about above.

So please understand my position: rewinding in the rate plugin never 
worked, so it cannot have any users that won't also be satisfied by 
being told that it should not work, always returning 0 and ignoring the 
rewind. Especially since the fundamental problem with state actually 
requires writing a new resampler implementation from scratch (see 
below), and since there are other plugins (such as AC3 encoder) where 
implementing proper rewinds is impossible without even larger efforts, 
so a program must be prepared to deal with this situation anyway. Of 
course, this is only my position, you are free to ignore it.

+++++

And now let's return to that fundamental problem with state. Inside the 
speculation block, I have suggested that the problem is that we don't 
have ops in snd_pcm_rate_ops_t that forward rewinds to the resampler 
implementation, so that it can restore its state to the one 
corresponding to an earlier moment in time. The real big problem (and 
the one I really meant in the original trolling attempt) is that even if 
you define new ops in snd_pcm_rate_ops_t, one can't implement them using 
existing open-source resamplers. Their APIs just don't have a function 
to rewind and to restore the prior magic samples.

=====

Now to the upmix plugin which does demonstrate a similar bug successfully.

With the following .asoundrc, the test program, when modified to run on 
the "upmix" pcm, produces silence in front channels and non-silence in 
back channels, indicating a bug in upmix.

pcm.!upmix {
     type upmix
     channels 6
     slave.pcm "hw:2"
}

The bug is now that the snd_pcm_extplug_callback structure does not have 
a callback that would notify the implementation about the rewind, so 
that the implementation restores its internal state (for upmix - a delay 
buffer) to a prior moment of time. Of course, implementability of this 
callback is again a valid question.


>
>> Should PulseAudio indeed use a complex dance with snd_pcm_rewindable()
>> to get the essentially-static bit of information whether a given PCM
>> supports full rewinds?
>
> As far as I can tell, ALSA assumes that _every_ device allows random
> access to its ring buffer.
>
> In particular, both alsa-lib's internal API and the explug/ioplug APIs,
> when writing samples, have no concept of "current position" and write to
> an explicitly specified position in the buffer.  The implementation
> if snd_pcm_rewindable() assumes that the entire buffer is accessible
> (the only plugin to change this is the file plugin).
>
> snd_pcm_rewindable/forwardable() are just for determining the possible
> amount of movement, not whether it's possible at all.

Understood.

> In other words: any plugin that does not allow random access is, as far
> as the ALSA API is concerned, buggy.

See (3).

>> 2) Use snd_pcm_forwardable() with an empty buffer just after setting
>>     hw_params: works (once the implementation in ioplug/extplug is
>>     fixed to return 0), doesn't require API additions,
>
> If ALSA is changed to track the information whether a device allows
> random access or not, this would be the correct way.

OK, I am willing to do this change, but I am a bit confused. Is the 
suggeestion to do (3), and, in addition, to add restrictions to 
snd_pcm_forwardable() that use the same underlying information?

>>     but David doesn't like it. He thinks that the sets of PCMs that
>>     support forwarding and rewinding may be different (here I disagree,
>>     but can't provide arguments).
>
> It's possible to imagine devices that implement forwarding by just
> inserting zero samples, but cannot go back in time.  (I'm not sure if
> implemting that would make any sense.)

No comments here.

>> 3) Add a new function to ALSA that gets this bit of static information
>>     for a given pcm handle, use it.
>
> I guess this will happen.

Do you agree with this prototype, and the fact that the function should 
be exportable?

int snd_pcm_hw_params_is_seekable(const snd_pcm_hw_params_t *params);

Check if the device fully supports rewinds and forwards.

Parameters:
     params	Configuration space

Return values:
     0	The whole plugin chain is not guaranteed to support arbitrary 
rewinds and forwards.
     1	The whole plugin chain is guaranteed, under all remaining 
configurations in the configuration space, to support any rewinds and 
forwards that don't move the application pointer through the hardware 
pointer.

If this function is called when there is more than one configuration 
exists in the configuration set (e.g. when the rate is not fixed), it 
may return pessimistic results.

-- 
Alexander E. Patrakov
-------------- next part --------------
A non-text attachment was scrubbed...
Name: pcm_rewind2.c
Type: text/x-csrc
Size: 3661 bytes
Desc: not available
URL: <http://mailman.alsa-project.org/pipermail/alsa-devel/attachments/20140425/893af939/attachment-0001.bin>


More information about the Alsa-devel mailing list