RAOP: Announce real latency

Use predefined values depending on the server, and make it configurable.
AirPlay is supposed to have 2s of latency. With my hardware, this is
more 2.352 seconds after numerous tests.
Switch from pausing/resuming the smoother to resetting it because the
smoother got stuck returning the same value after an idle/running cycle,
making latency calculation wrong.
This commit is contained in:
Colin Leroy 2017-09-17 20:46:49 +02:00 committed by Tanu Kaskinen
parent 61217528a1
commit d8a2cef360
4 changed files with 62 additions and 5 deletions

View file

@ -44,11 +44,14 @@
#include <pulsecore/avahi-wrap.h> #include <pulsecore/avahi-wrap.h>
#include "module-raop-discover-symdef.h" #include "module-raop-discover-symdef.h"
#include "raop-util.h"
PA_MODULE_AUTHOR("Colin Guthrie"); PA_MODULE_AUTHOR("Colin Guthrie");
PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Discovery of RAOP devices"); PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Discovery of RAOP devices");
PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_VERSION(PACKAGE_VERSION);
PA_MODULE_LOAD_ONCE(true); PA_MODULE_LOAD_ONCE(true);
PA_MODULE_USAGE(
"latency_msec=<audio latency - applies to all devices> ");
#define SERVICE_TYPE_SINK "_raop._tcp" #define SERVICE_TYPE_SINK "_raop._tcp"
@ -61,9 +64,13 @@ struct userdata {
AvahiServiceBrowser *sink_browser; AvahiServiceBrowser *sink_browser;
pa_hashmap *tunnels; pa_hashmap *tunnels;
bool latency_set;
uint32_t latency;
}; };
static const char* const valid_modargs[] = { static const char* const valid_modargs[] = {
"latency_msec",
NULL NULL
}; };
@ -127,6 +134,23 @@ static void tunnel_free(struct tunnel *t) {
pa_xfree(t); pa_xfree(t);
} }
/* This functions returns RAOP audio latency as guessed by the
* device model header.
* Feel free to complete the possible values after testing with
* your hardware.
*/
static uint32_t guess_latency_from_device(const char *model) {
uint32_t default_latency = RAOP_DEFAULT_LATENCY;
if (pa_streq(model, "PIONEER,1")) {
/* Pioneer N-30 */
default_latency = 2352;
}
pa_log_debug("Default latency is %u ms for device model %s.", default_latency, model);
return default_latency;
}
static void resolver_cb( static void resolver_cb(
AvahiServiceResolver *r, AvahiServiceResolver *r,
AvahiIfIndex interface, AvahiProtocol protocol, AvahiIfIndex interface, AvahiProtocol protocol,
@ -145,6 +169,7 @@ static void resolver_cb(
char at[AVAHI_ADDRESS_STR_MAX]; char at[AVAHI_ADDRESS_STR_MAX];
AvahiStringList *l; AvahiStringList *l;
pa_module *m; pa_module *m;
uint32_t latency = RAOP_DEFAULT_LATENCY;
pa_assert(u); pa_assert(u);
@ -226,6 +251,9 @@ static void resolver_cb(
/* Sample rate */ /* Sample rate */
pa_xfree(sr); pa_xfree(sr);
sr = pa_xstrdup(value); sr = pa_xstrdup(value);
} else if (pa_streq(key, "am")) {
/* Device model */
latency = guess_latency_from_device(value);
} }
avahi_free(key); avahi_free(key);
@ -308,6 +336,13 @@ static void resolver_cb(
pa_xfree(t); pa_xfree(t);
} }
if (u->latency_set)
latency = u->latency;
t = args;
args = pa_sprintf_malloc("%s latency_msec=%u", args, latency);
pa_xfree(t);
pa_log_debug("Loading module-raop-sink with arguments '%s'", args); pa_log_debug("Loading module-raop-sink with arguments '%s'", args);
if (pa_module_load(&m, u->core, "module-raop-sink", args) >= 0) { if (pa_module_load(&m, u->core, "module-raop-sink", args) >= 0) {
@ -432,10 +467,17 @@ int pa__init(pa_module *m) {
goto fail; goto fail;
} }
m->userdata = u = pa_xnew(struct userdata, 1); m->userdata = u = pa_xnew0(struct userdata, 1);
u->core = m->core; u->core = m->core;
u->module = m; u->module = m;
u->sink_browser = NULL;
if (pa_modargs_get_value(ma, "latency_msec", NULL) != NULL) {
u->latency_set = true;
if (pa_modargs_get_value_u32(ma, "latency_msec", &u->latency) < 0) {
pa_log("Failed to parse latency_msec argument.");
goto fail;
}
}
u->tunnels = pa_hashmap_new(tunnel_hash, tunnel_compare); u->tunnels = pa_hashmap_new(tunnel_hash, tunnel_compare);

View file

@ -46,7 +46,8 @@ PA_MODULE_USAGE(
"rate=<sample rate> " "rate=<sample rate> "
"channels=<number of channels> " "channels=<number of channels> "
"username=<authentication user name, default: \"iTunes\"> " "username=<authentication user name, default: \"iTunes\"> "
"password=<authentication password>"); "password=<authentication password> "
"latency_msec=<audio latency>");
static const char* const valid_modargs[] = { static const char* const valid_modargs[] = {
"name", "name",
@ -61,6 +62,7 @@ static const char* const valid_modargs[] = {
"channels", "channels",
"username", "username",
"password", "password",
"latency_msec",
NULL NULL
}; };

View file

@ -63,6 +63,7 @@
#include "raop-sink.h" #include "raop-sink.h"
#include "raop-client.h" #include "raop-client.h"
#include "raop-util.h"
struct userdata { struct userdata {
pa_core *core; pa_core *core;
@ -87,6 +88,8 @@ struct userdata {
pa_usec_t start; pa_usec_t start;
pa_smoother *smoother; pa_smoother *smoother;
uint64_t write_count; uint64_t write_count;
uint32_t latency;
}; };
enum { enum {
@ -119,6 +122,9 @@ static int64_t sink_get_latency(const struct userdata *u) {
latency = pa_bytes_to_usec(u->write_count, &u->sink->sample_spec) - (int64_t) now; latency = pa_bytes_to_usec(u->write_count, &u->sink->sample_spec) - (int64_t) now;
/* RAOP default latency */
latency += u->latency * PA_USEC_PER_MSEC;
return latency; return latency;
} }
@ -136,7 +142,6 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state)); pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state));
pa_smoother_pause(u->smoother, pa_rtclock_now());
/* Issue a TEARDOWN if we are still connected */ /* Issue a TEARDOWN if we are still connected */
if (pa_raop_client_is_alive(u->raop)) { if (pa_raop_client_is_alive(u->raop)) {
pa_raop_client_teardown(u->raop); pa_raop_client_teardown(u->raop);
@ -163,7 +168,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
pa_log_debug("RAOP: RUNNING"); pa_log_debug("RAOP: RUNNING");
now = pa_rtclock_now(); now = pa_rtclock_now();
pa_smoother_resume(u->smoother, now, true); pa_smoother_reset(u->smoother, now, false);
if (!pa_raop_client_is_alive(u->raop)) { if (!pa_raop_client_is_alive(u->raop)) {
/* Connecting will trigger a RECORD and start steaming */ /* Connecting will trigger a RECORD and start steaming */
@ -494,6 +499,12 @@ pa_sink* pa_raop_sink_new(pa_module *m, pa_modargs *ma, const char *driver) {
u->thread = NULL; u->thread = NULL;
u->rtpoll = pa_rtpoll_new(); u->rtpoll = pa_rtpoll_new();
u->rtpoll_item = NULL; u->rtpoll_item = NULL;
u->latency = RAOP_DEFAULT_LATENCY;
if (pa_modargs_get_value_u32(ma, "latency_msec", &u->latency) < 0) {
pa_log("Failed to parse latency_msec argument");
goto fail;
}
if (pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll) < 0) { if (pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll) < 0) {
pa_log("pa_thread_mq_init() failed."); pa_log("pa_thread_mq_init() failed.");

View file

@ -27,6 +27,8 @@
Kungliga Tekniska högskolan. Kungliga Tekniska högskolan.
***/ ***/
#define RAOP_DEFAULT_LATENCY 2000 /* msec */
int pa_raop_base64_encode(const void *data, int len, char **str); int pa_raop_base64_encode(const void *data, int len, char **str);
int pa_raop_base64_decode(const char *str, void *data); int pa_raop_base64_decode(const char *str, void *data);