It's possible that the memblockq of a sink input is rewound to a negative read
index if the sink input is moved between sinks shortly after its creation. When
this happens, pa_memblockq_peek() returns a memchunk whose 'memblock' field is
NULL and whose 'length' field indicates the length of the gap caused by the
negative read index. This will trigger an assert in play-memblockq.c.
If the memblockq had a silence memchunk, pa_memblockq_peek() would return
silence for the duration of the gap and the assert would be avoided. However,
this approach would prevent the sink input from being drained and is thus not
possible. Instead, we handle the aforementioned situation by dropping the gap
indicated by the 'length' field of the memchunk and by peeking the actual data
that comes after the gap.
This scenario seems to be quite rare in everyday use, but it causes a severe
bug in the handheld world. The assert can be triggered e.g. by loading two null
sinks, playing a sample from the cache to one of them and then moving the
created sink input between the two sinks. The rewinds done by the null sinks
seem to be quite long (I don't know if this is normal behaviour or something
fishy in module-null-sink).
See also:
6bd34156b130c07b130de10111a12ef6dab18b52
virtual-sink: Fix a crash when moving the sink to a new master right after setup.
https://tango.0pointer.de/pipermail/pulseaudio-discuss/2011-February/009105.html
Reproduce:
This problem can be reproduced with the following script:
SAMPLE_PATH="/usr/share/sounds/alsa/"
SAMPLE="Front_Left"
pactl remove-sample $SAMPLE 2> /dev/null
pactl upload-sample $SAMPLE_PATH$SAMPLE.wav
mod1=`pactl load-module module-null-sink sink_name=null1`
mod2=`pactl load-module module-null-sink sink_name=null2`
pactl play-sample $SAMPLE null1
input=`pactl list | grep "Sink Input #" | tail -n 1 | cut -d# -f2`
echo "Sample $SAMPLE playing as Sink Input #$input"
pactl move-sink-input $input null2
pactl move-sink-input $input null1
pactl unload-module $mod1
pactl unload-module $mod2
return -1;
}
- /* FIXME: u->memblockq doesn't have a silence memchunk set, so
- * pa_memblockq_peek() will return 0 without returning any memblock if the
- * read index points to a hole. If the memblockq is rewound beyond index 0,
- * then there will be a hole. */
- pa_assert(chunk->memblock);
+ /* If there's no memblock, there's going to be data in the memblockq after
+ * a gap with length chunk->length. Drop the the gap and peek the actual
+ * data. There should always be some data coming - hence the assert. The
+ * gap will occur if the memblockq is rewound beyond index 0.*/
+ if (!chunk->memblock) {
+ pa_memblockq_drop(u->memblockq, chunk->length);
+ pa_assert_se(pa_memblockq_peek(u->memblockq, chunk) >= 0);
+ }
chunk->length = PA_MIN(chunk->length, nbytes);
pa_memblockq_drop(u->memblockq, chunk->length);