From: Lennart Poettering Date: Wed, 14 May 2008 00:43:24 +0000 (+0000) Subject: some fixes to make the esound protocol work on glitch-free again X-Git-Url: https://code.delx.au/pulseaudio/commitdiff_plain/94c269e0f4f6c33c0d4f0be66a17504fe1561094 some fixes to make the esound protocol work on glitch-free again git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/glitch-free@2415 fefdeb5f-60dc-0310-8127-8f9354f1896f --- diff --git a/src/pulsecore/protocol-esound.c b/src/pulsecore/protocol-esound.c index 388808a5..492dc9fa 100644 --- a/src/pulsecore/protocol-esound.c +++ b/src/pulsecore/protocol-esound.c @@ -70,10 +70,12 @@ #define PLAYBACK_BUFFER_SECONDS (.25) #define PLAYBACK_BUFFER_FRAGMENTS (10) #define RECORD_BUFFER_SECONDS (5) -#define RECORD_BUFFER_FRAGMENTS (100) #define MAX_CACHE_SAMPLE_SIZE (2048000) +#define DEFAULT_SINK_LATENCY (150*PA_USEC_PER_MSEC) +#define DEFAULT_SOURCE_LATENCY (150*PA_USEC_PER_MSEC) + #define SCACHE_PREFIX "esound." /* This is heavily based on esound's code */ @@ -102,8 +104,9 @@ typedef struct connection { struct { pa_memblock *current_memblock; - size_t memblock_index, fragment_size; + size_t memblock_index; pa_atomic_t missing; + pa_bool_t underrun; } playback; struct { @@ -122,7 +125,7 @@ static PA_DEFINE_CHECK_TYPE(connection, pa_msgobject); struct pa_protocol_esound { pa_module *module; pa_core *core; - int public; + pa_bool_t public; pa_socket_server *server; pa_idxset *connections; @@ -150,6 +153,8 @@ typedef struct proto_handler { } esd_proto_handler_info_t; static int sink_input_pop_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk); +static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes); +static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes); static void sink_input_kill_cb(pa_sink_input *i); static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk); static pa_usec_t source_output_get_latency_cb(pa_source_output *o); @@ -397,8 +402,7 @@ static int esd_proto_stream_play(connection *c, PA_GCC_UNUSED esd_proto_t reques CHECK_VALIDITY(sink, "No such sink: %s", c->protocol->sink_name); } - strncpy(name, data, sizeof(name)); - name[sizeof(name)-1] = 0; + pa_strlcpy(name, data, sizeof(name)); utf8_name = pa_utf8_filter(name); pa_client_set_name(c->client, utf8_name); @@ -425,20 +429,23 @@ static int esd_proto_stream_play(connection *c, PA_GCC_UNUSED esd_proto_t reques c->input_memblockq = pa_memblockq_new( 0, l, - 0, + l, pa_frame_size(&ss), (size_t) -1, l/PLAYBACK_BUFFER_FRAGMENTS, 0, NULL); - pa_iochannel_socket_set_rcvbuf(c->io, l/PLAYBACK_BUFFER_FRAGMENTS*2); - c->playback.fragment_size = l/PLAYBACK_BUFFER_FRAGMENTS; + pa_iochannel_socket_set_rcvbuf(c->io, l); c->sink_input->parent.process_msg = sink_input_process_msg; c->sink_input->pop = sink_input_pop_cb; + c->sink_input->process_rewind = sink_input_process_rewind_cb; + c->sink_input->update_max_rewind = sink_input_update_max_rewind_cb; c->sink_input->kill = sink_input_kill_cb; c->sink_input->userdata = c; + pa_sink_input_set_requested_latency(c->sink_input, DEFAULT_SINK_LATENCY); + c->state = ESD_STREAMING_DATA; c->protocol->n_player++; @@ -498,8 +505,7 @@ static int esd_proto_stream_record(connection *c, esd_proto_t request, const voi } } - strncpy(name, data, sizeof(name)); - name[sizeof(name)-1] = 0; + pa_strlcpy(name, data, sizeof(name)); utf8_name = pa_utf8_filter(name); pa_client_set_name(c->client, utf8_name); @@ -520,25 +526,27 @@ static int esd_proto_stream_record(connection *c, esd_proto_t request, const voi c->source_output = pa_source_output_new(c->protocol->core, &sdata, 0); pa_source_output_new_data_done(&sdata); - CHECK_VALIDITY(c->source_output, "Failed to create source_output."); + CHECK_VALIDITY(c->source_output, "Failed to create source output."); l = (size_t) (pa_bytes_per_second(&ss)*RECORD_BUFFER_SECONDS); c->output_memblockq = pa_memblockq_new( 0, l, - 0, + l, pa_frame_size(&ss), 1, 0, 0, NULL); - pa_iochannel_socket_set_sndbuf(c->io, l/RECORD_BUFFER_FRAGMENTS*2); + pa_iochannel_socket_set_sndbuf(c->io, l); c->source_output->push = source_output_push_cb; c->source_output->kill = source_output_kill_cb; c->source_output->get_latency = source_output_get_latency_cb; c->source_output->userdata = c; + pa_source_output_set_requested_latency(c->source_output, DEFAULT_SOURCE_LATENCY); + c->state = ESD_STREAMING_DATA; c->protocol->n_player++; @@ -789,8 +797,7 @@ static int esd_proto_sample_cache(connection *c, PA_GCC_UNUSED esd_proto_t reque CHECK_VALIDITY(sc_length <= MAX_CACHE_SAMPLE_SIZE, "Sample too large (%d bytes).", (int)sc_length); strcpy(name, SCACHE_PREFIX); - strncpy(name+sizeof(SCACHE_PREFIX)-1, data, ESD_NAME_MAX); - name[sizeof(name)-1] = 0; + pa_strlcpy(name+sizeof(SCACHE_PREFIX)-1, data, ESD_NAME_MAX); CHECK_VALIDITY(pa_utf8_valid(name), "Invalid UTF8 in sample name."); @@ -822,8 +829,7 @@ static int esd_proto_sample_get_id(connection *c, PA_GCC_UNUSED esd_proto_t requ pa_assert(length == ESD_NAME_MAX); strcpy(name, SCACHE_PREFIX); - strncpy(name+sizeof(SCACHE_PREFIX)-1, data, ESD_NAME_MAX); - name[sizeof(name)-1] = 0; + pa_strlcpy(name+sizeof(SCACHE_PREFIX)-1, data, ESD_NAME_MAX); CHECK_VALIDITY(pa_utf8_valid(name), "Invalid UTF8 in sample name."); @@ -1016,6 +1022,7 @@ static int do_read(connection *c) { ssize_t r; size_t l; void *p; + size_t space; pa_assert(c->input_memblockq); @@ -1024,21 +1031,26 @@ static int do_read(connection *c) { if (!(l = pa_atomic_load(&c->playback.missing))) return 0; - if (l > c->playback.fragment_size) - l = c->playback.fragment_size; + if (c->playback.current_memblock) { + + space = pa_memblock_get_length(c->playback.current_memblock) - c->playback.memblock_index; - if (c->playback.current_memblock) - if (pa_memblock_get_length(c->playback.current_memblock) - c->playback.memblock_index < l) { + if (space <= 0) { pa_memblock_unref(c->playback.current_memblock); c->playback.current_memblock = NULL; - c->playback.memblock_index = 0; } + } if (!c->playback.current_memblock) { - pa_assert_se(c->playback.current_memblock = pa_memblock_new(c->protocol->core->mempool, c->playback.fragment_size*2)); + pa_assert_se(c->playback.current_memblock = pa_memblock_new(c->protocol->core->mempool, 0)); 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); @@ -1126,12 +1138,11 @@ static void do_work(connection *c) { if (c->dead) return; - if (pa_iochannel_is_readable(c->io)) { + if (pa_iochannel_is_readable(c->io)) if (do_read(c) < 0) goto fail; - } - if (c->state == ESD_STREAMING_DATA && c->source_output && pa_iochannel_is_hungup(c->io)) + if (c->state == ESD_STREAMING_DATA && !c->sink_input && pa_iochannel_is_hungup(c->io)) /* In case we are in capture mode we will never call read() * on the socket, hence we need to detect the hangup manually * here, instead of simply waiting for read() to return 0. */ @@ -1216,15 +1227,19 @@ static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int /* New data from the main loop */ pa_memblockq_push_align(c->input_memblockq, chunk); + if (pa_memblockq_is_readable(c->input_memblockq) && c->playback.underrun) { + pa_log_debug("Requesting rewind due to end of underrun."); + pa_sink_input_request_rewind(c->sink_input, 0, FALSE, TRUE); + } + /* pa_log("got data, %u", pa_memblockq_get_length(c->input_memblockq)); */ return 0; } - case SINK_INPUT_MESSAGE_DISABLE_PREBUF: { + case SINK_INPUT_MESSAGE_DISABLE_PREBUF: pa_memblockq_prebuf_disable(c->input_memblockq); return 0; - } case PA_SINK_INPUT_MESSAGE_GET_LATENCY: { pa_usec_t *r = userdata; @@ -1243,32 +1258,60 @@ static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int /* Called from thread context */ static int sink_input_pop_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) { connection*c; - int r; - pa_assert(i); + pa_sink_input_assert_ref(i); c = CONNECTION(i->userdata); connection_assert_ref(c); pa_assert(chunk); - if ((r = pa_memblockq_peek(c->input_memblockq, chunk)) < 0) { + if (pa_memblockq_peek(c->input_memblockq, chunk) < 0) { + c->playback.underrun = TRUE; if (c->dead && pa_sink_input_safe_to_remove(i)) pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_UNLINK_CONNECTION, NULL, 0, NULL, NULL); + + return -1; } else { - size_t old, new; + size_t m; + + c->playback.underrun = FALSE; - old = pa_memblockq_missing(c->input_memblockq); pa_memblockq_drop(c->input_memblockq, chunk->length); - new = pa_memblockq_missing(c->input_memblockq); + m = pa_memblockq_pop_missing(c->input_memblockq); - if (new > old) { - if (pa_atomic_add(&c->playback.missing, new - old) <= 0) + if (m > 0) + if (pa_atomic_add(&c->playback.missing, m) <= 0) pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_REQUEST_DATA, NULL, 0, NULL, NULL); - } + + return 0; } +} + +/* Called from thread context */ +static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { + connection *c; - return r; + pa_sink_input_assert_ref(i); + c = CONNECTION(i->userdata); + connection_assert_ref(c); + + /* If we are in an underrun, then we don't rewind */ + if (i->thread_info.underrun_for > 0) + return; + + pa_memblockq_rewind(c->input_memblockq, nbytes); +} + +/* Called from thread context */ +static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { + connection *c; + + pa_sink_input_assert_ref(i); + c = CONNECTION(i->userdata); + connection_assert_ref(c); + + pa_memblockq_set_maxrewind(c->input_memblockq, nbytes); } static void sink_input_kill_cb(pa_sink_input *i) { @@ -1283,7 +1326,7 @@ static void sink_input_kill_cb(pa_sink_input *i) { static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) { connection *c; - pa_assert(o); + pa_source_output_assert_ref(o); c = CONNECTION(o->userdata); pa_assert(c); pa_assert(chunk); @@ -1300,7 +1343,7 @@ static void source_output_kill_cb(pa_source_output *o) { static pa_usec_t source_output_get_latency_cb(pa_source_output *o) { connection*c; - pa_assert(o); + pa_source_output_assert_ref(o); c = CONNECTION(o->userdata); pa_assert(c); @@ -1346,6 +1389,7 @@ static void on_connection(pa_socket_server*s, pa_iochannel *io, void *userdata) pa_iochannel_socket_peer_to_string(io, pname, sizeof(pname)); pa_snprintf(cname, sizeof(cname), "EsounD client (%s)", pname); c->client = pa_client_new(p->core, __FILE__, cname); + pa_proplist_sets(c->client->proplist, "esound-protocol.peer", pname); c->client->module = p->module; c->client->kill = client_kill_cb; c->client->userdata = c; @@ -1371,11 +1415,10 @@ static void on_connection(pa_socket_server*s, pa_iochannel *io, void *userdata) c->playback.current_memblock = NULL; c->playback.memblock_index = 0; - c->playback.fragment_size = 0; + c->playback.underrun = TRUE; pa_atomic_store(&c->playback.missing, 0); - c->scache.memchunk.length = c->scache.memchunk.index = 0; - c->scache.memchunk.memblock = NULL; + pa_memchunk_reset(&c->scache.memchunk); c->scache.name = NULL; c->original_name = NULL; @@ -1456,7 +1499,8 @@ void pa_protocol_esound_free(pa_protocol_esound *p) { connection_unlink(c); pa_idxset_free(p->connections, NULL, NULL); - pa_socket_server_unref(p->server); + if (p->server) + pa_socket_server_unref(p->server); if (p->auth_ip_acl) pa_ip_acl_free(p->auth_ip_acl);