Who is using pa_core->mempool and why!?
core-scache
What is the core-scache residing at src/pulsecore/core-scache.c
?
This is officially documented in the "Sample Cache" section of pulse-cli-syntax(5).
The commands include:
- list-samples: List content of the sample cache
- load-sample
name
filename
: Read audio filename and store its contents in the sample name "name" - load-sample-lazy
name
filename
: Sample asload-sample
but caching the audio file only upon first use - load-sample-dir-lazy
path
: Same as above, but loading an entire directory of audio files in the process! In this case, sample name is the filename basename - remove-sample
name
: Remove the sample with name from the path - play-sample
name
: Play given sample!
As we can see from above, this does not need to share any data with the client. And thus, let's move this to the server-only mempool.
An example of the above commands could be:
[darwish]$ pacmd load-sample guitar ~/guitar.wav
[darwish]$ pacmd play-sample guitar
You need to specify a sample name and a sink name.
[darwish]$ pacmd play-sample guitar alsa_output.pci-0000_00_14.2.analog-stereo
Playing on sink input #0
play-memchunk (the silence memchunk)
This file src/pulsecore/play-memchunk.c
only contains one poor function pa_play_memchunk()
.
pa_play_memchunk()
is solely used by pa_scache_play_item()
, which is the function executed for the Sample Cache command play-sample. Inspecting this method, we see:
int pa_play_memchunk(
pa_sink *sink,
const pa_sample_spec *ss,
const pa_channel_map *map,
const pa_memchunk *chunk,
pa_cvolume *volume,
pa_proplist *p,
pa_sink_input_flags_t flags,
uint32_t *sink_input_index) {
pa_memblockq *q;
pa_memchunk silence;
pa_silence_memchunk_get(&sink->core->silence_cache, sink->core->mempool, &silence, ss, 0);
q = pa_memblockq_new("pa_play_memchunk() q", 0, chunk->length, 0, ss, 1, 1, 0, &silence);
...
}
And the pa_silence_memchunk_get()
is as follows:
pa_memchunk* pa_silence_memchunk_get(pa_silence_cache *cache, pa_mempool *pool, pa_memchunk* ret, const pa_sample_spec *spec, size_t length) {
pa_memblock *b;
size_t l;
if (!(b = cache->blocks[spec->format]))
switch (spec->format) {
case PA_SAMPLE_U8:
cache->blocks[PA_SAMPLE_U8] = b = silence_memblock_new(pool, 0x80);
break;
case PA_SAMPLE_S16LE:
case PA_SAMPLE_S16BE:
case PA_SAMPLE_S32LE:
case PA_SAMPLE_S32BE:
case PA_SAMPLE_S24LE:
case PA_SAMPLE_S24BE:
case PA_SAMPLE_S24_32LE:
case PA_SAMPLE_S24_32BE:
case PA_SAMPLE_FLOAT32LE:
case PA_SAMPLE_FLOAT32BE:
cache->blocks[PA_SAMPLE_S16LE] = b = silence_memblock_new(pool, 0);
cache->blocks[PA_SAMPLE_S16BE] = pa_memblock_ref(b);
cache->blocks[PA_SAMPLE_S32LE] = pa_memblock_ref(b);
cache->blocks[PA_SAMPLE_S32BE] = pa_memblock_ref(b);
cache->blocks[PA_SAMPLE_S24LE] = pa_memblock_ref(b);
cache->blocks[PA_SAMPLE_S24BE] = pa_memblock_ref(b);
cache->blocks[PA_SAMPLE_S24_32LE] = pa_memblock_ref(b);
cache->blocks[PA_SAMPLE_S24_32BE] = pa_memblock_ref(b);
cache->blocks[PA_SAMPLE_FLOAT32LE] = pa_memblock_ref(b);
cache->blocks[PA_SAMPLE_FLOAT32BE] = pa_memblock_ref(b);
break;
case PA_SAMPLE_ALAW:
cache->blocks[PA_SAMPLE_ALAW] = b = silence_memblock_new(pool, 0xd5);
break;
case PA_SAMPLE_ULAW:
cache->blocks[PA_SAMPLE_ULAW] = b = silence_memblock_new(pool, 0xff);
break;
default:
pa_assert_not_reached();
}
...
}
So in this usage case of pa_silence_memchunk_get()
, the client need not to access any of such memory. Thus use pure daemon mempool instead.
For detailed explanations on what a memchunk is, check our Memory Chunks and Memblockq section.
protocol-simple
We've discussed the PA simple API earlier. Why is the core mempool used here?
The mempool is used by the entry point pa_simple_protocol_connect()
:
void pa_simple_protocol_connect(pa_simple_protocol *p, pa_iochannel *io, pa_simple_options *o) {
connection *c = NULL;
char pname[128];
pa_client_new_data client_data;
pa_assert(p);
pa_assert(io);
pa_assert(o);
if (pa_idxset_size(p->connections)+1 > MAX_CONNECTIONS) {
pa_log("Warning! Too many connections (%u), dropping incoming connection.", MAX_CONNECTIONS);
pa_iochannel_free(io);
return;
}
c = pa_msgobject_new(connection);
c->parent.parent.free = connection_free;
c->parent.process_msg = connection_process_msg;
c->io = io;
pa_iochannel_set_callback(c->io, io_callback, c);
...
Note that this function, unlike other simple APIs, seems to be exclusively used the server. This is especially true if you note the error messages "Too many connections".
As you can see above, for any iochannel events, the io_callback
is called:
static void io_callback(pa_iochannel*io, void *userdata) {
connection *c = CONNECTION(userdata);
connection_assert_ref(c);
pa_assert(io);
do_work(c);
}
And do work does:
static void do_work(connection *c) {
connection_assert_ref(c);
if (c->dead)
return;
if (pa_iochannel_is_readable(c->io))
if (do_read(c) < 0)
goto fail;
if (!c->sink_input && pa_iochannel_is_hungup(c->io))
goto fail;
while (pa_iochannel_is_writable(c->io)) {
int r = do_write(c);
if (r < 0)
goto fail;
if (r == 0)
break;
}
return;
And as you can see, this is just a simple async polling mechanism where a read or write is done only if the socket signalled availablity for doing so.
static int do_read(connection *c) {
pa_memchunk chunk;
ssize_t r;
size_t l;
void *p;
size_t space = 0;
connection_assert_ref(c);
if (!c->sink_input || (l = (size_t) pa_atomic_load(&c->playback.missing)) <= 0)
return 0;
if (c->playback.current_memblock) {
space = pa_memblock_get_length(c->playback.current_memblock) - c->playback.memblock_index;
if (space <= 0) {
pa_memblock_unref(c->playback.current_memblock);
c->playback.current_memblock = NULL;
}
}
if (!c->playback.current_memblock) {
pa_assert_se(c->playback.current_memblock = pa_memblock_new(c->protocol->core->mempool, (size_t) -1));
c->playback.memblock_index = 0;
space = pa_memblock_get_length(c->playback.current_memblock);
}
if (l > space)
l = space;
p = pa_memblock_acquire(c->playback.current_memblock);
r = pa_iochannel_read(c->io, (uint8_t*) p + c->playback.memblock_index, l);
pa_memblock_release(c->playback.current_memblock);
if (r <= 0) {
if (r < 0 && (errno == EINTR || errno == EAGAIN))
return 0;
pa_log_debug("read(): %s", r == 0 ? "EOF" : pa_cstrerror(errno));
return -1;
}
chunk.memblock = c->playback.current_memblock;
chunk.index = c->playback.memblock_index;
chunk.length = (size_t) r;
c->playback.memblock_index += (size_t) r;
pa_asyncmsgq_post(c->sink_input->sink->asyncmsgq, PA_MSGOBJECT(c->sink_input), SINK_INPUT_MESSAGE_POST_DATA, NULL, 0, &chunk, NULL);
pa_atomic_sub(&c->playback.missing, (int) r);
return 0;
}
Note that in the above, do_read()
will be called only if the iochannel signalled some data to be read. The read itself will not occur except if playback.missing
was bigger than 0.
If no memblock was allocated, it's allocated from the core memblock. Afterwards, data are read from the iochannel into the appropriate memblock address p + memblock_index
.
A "memory chunk" is built referencing the memory area that was just written! (Read the description of memory chunks above for further details). It just references the memblock itself, pointer to the chunk of read data within the memblock, and length of that data!
Afterwards, the memchunk is sent as a payload with the message SINK_INPUT_MESSAGE_POST_DATA
.
What is playback.missing
Note that all of the above only happens if connection->playback.missing
is bigger than zero. But what really is this value?
Semantics of the "missing" parameter is documented in the Memory Chunks and Memblockq section.