[PATCH] add module-tunnel-sink-new: experimental rewrite of module-tunnel using libpulse. Old module-tunnel shares duplicated functionality with libpulse because it is implementing pulse protocol again. It is also not as much tested as libpulse it and not refactored.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Wed, 14 Aug 2013 14:42:16 +0300
Tanu Kaskinen <tanu.kaskinen at linux.intel.com> wrote:

> On Wed, 2013-08-07 at 18:05 +0200, Alexander Couzens wrote:
> > Signed-off-by: Alexander Couzens <lynxis at fe80.eu>
> > ---
> 
> The whole commit message, apart from the signed-off-by line, is now all
> on one line. The first line is supposed to be a very short summary of
> the changes, suitable for e.g. email subject line. Further explanation
> should go in a separate paragraph.
sorry. this was not my intention.
> 

> > +
> > +/* libpulse callbacks */
> > +static void stream_state_callback(pa_stream *stream, void *userdata);
> 
> Cosmetic: I'd prefer _cb suffix for all callbacks.
is fixed.
> 
> > +static void context_state_callback(pa_context *c, void *userdata);
> > +static void sink_update_requested_latency_cb(pa_sink *s);
> > +
> > +struct userdata {
> > +    pa_module *module;
> > +    pa_sink *sink;
> > +    pa_thread *thread;
> > +    pa_thread_mq thread_mq;
> > +    pa_mainloop *thread_mainloop;
> > +    pa_mainloop_api *thread_mainloop_api;
> > +
> > +    /* libpulse context */
> 
> In the previous review I said that this comment doesn't have any useful
> information. I meant to imply that the comment should be removed.
removed.
> > +    pa_context *context;
> > +    pa_stream *stream;
> > +
> > +    pa_buffer_attr bufferattr;
> > +
> > +    bool connected;
> > +
> > +    char *remote_server;
> > +    char *remote_sink_name;
> > +};
> > +
> > +static const char* const valid_modargs[] = {
> > +    "sink_name",
> > +    "sink_properties",
> > +    "server",
> > +    "sink",
> > +    "format",
> > +    "channels",
> > +    "rate",
> > +    "channel_map",
> > +    "cookie", /* unimplemented */
> > +    "reconnect", /* reconnect if server comes back again - unimplemented*/
> 
> "cookie" and "reconnect" aren't included in the usage string. I guess
> the reason is because the features haven't been implemented, but I think
> it would be good to keep PA_MODULE_USAGE and valid_modargs in sync,
> because when you implement the cookie support, for example, it's very
> easy to forget to update PA_MODULE_USAGE.
ok. I'll extend the comment.
> 
> What's your plan for implementing "reconnect"? I argued in my previous
> review that module-tunnel-sink shouldn't support reconnecting at all,
> and should instead leave that job for module-zeroconf-discover.
imagine you have a network with 10 small boxes (let's say raspberry) with usbsound devices.
some of them disappear from time to time, because somebody cut their power off ;). they are so small, that avahi does not fit on it.
today there is a crontab which runs every minute and loads a lot of mod-tunnel module with their specific ips.
for that i would like to have a opt-in feature.

> > +    proplist = tunnel_new_proplist(u);
> > +    /* init libpulse */
> > +    u->context = pa_context_new_with_proplist(pa_mainloop_get_api(u->thread_mainloop),
> 
> Nitpick: using u->thread_mainloop_api instead of pa_mainloop_get_api()
> would be slightly simpler/shorter.
But you can not change the mainloop implementation sometime.
> 
> > +                                              "PulseAudio",
> > +                                              proplist);
> > +    pa_proplist_free(proplist);
> > +
> > +    if (!u->context) {
> > +        pa_log("Failed to create libpulse context");
> > +        goto fail;
> > +    }
> > +
> > +    pa_context_set_state_callback(u->context, context_state_callback, u);
> > +    if (pa_context_connect(u->context,
> > +                           u->remote_server,
> > +                           PA_CONTEXT_NOFAIL | PA_CONTEXT_NOAUTOSPAWN,
> 
> As I explained in the previous review, the NOFAIL flag serves no purpose
> here.
You did it. I didn't removed it, because I thought first fix the docu and
remove it afterwards.
> > +        if (u->connected &&
> > +                PA_STREAM_IS_GOOD(pa_stream_get_state(u->stream)) &&
> > +                PA_SINK_IS_LINKED(u->sink->thread_info.state)) {
> > +            /* TODO: use IS_RUNNING + cork stream */
> 
> What do you mean by this comment?
Implement corking instead of sending silence
> 
> > +
> > +            if (pa_stream_is_corked(u->stream)) {
> > +                pa_stream_cork(u->stream, 0, NULL, NULL);
> 
> I already pointed out in the previous review that you need to unref the
> pa_operation object that this function returns.
This doesn't work, because it doesn't return an operation and if I'm doing it crashed because of
dereferencing a nullpointer.
> 
> > +            } else {
> > +                writable = pa_stream_writable_size(u->stream);
> > +                if (writable > 0) {
> > +                    if (memchunk.length <= 0)
> 
> I already pointed out in the previous review that the length is always
> zero here. That means that checking the value is pointless.
>
> > +                        pa_sink_render(u->sink, writable, &memchunk);
> 
> I think pa_sink_render_full() would be better. It's nicer to send one
> big packet over the network than several smaller ones (or that's at
> least my gut feeling)
ok.
> > +
> > +                    pa_assert(memchunk.length > 0);
> > +
> > +                    /* we have new data to write */
> > +                    p = (const uint8_t *) pa_memblock_acquire(memchunk.memblock);
> > +                    /* TODO: ZERO COPY! */
> 
> How do you plan to achieve zero copy? If the plan is to use
> pa_stream_write_begin(), that won't result in zero copy, because the
> data needs to be copied to the buffer that pa_stream_write_begin() gives
> (but it's still a good idea to use pa_stream_write_begin()).
I thought that would be the solution because we can render direct in the buffer from libpulse.
> > +                    ret = pa_stream_write(u->stream,
> > +                                        ((uint8_t*) p + memchunk.index),
> > +                                        memchunk.length,
> > +                                        NULL,     /**< A cleanup routine for the data or NULL to request an internal copy */
> > +                                        0,        /** offset */
> > +                                        PA_SEEK_RELATIVE
> > +                                        );
> 
> The indentation is still off by two spaces here.
> 
> > +                    pa_memblock_release(memchunk.memblock);
> > +                    pa_memblock_unref(memchunk.memblock);
> > +                    pa_memchunk_reset(&memchunk);
> > +
> > +                    if (ret != 0) {
> > +                        /* TODO: we should consider a state change or is that already done ? */
> > +                        pa_log_warn("Could not write data into the stream ... ret = %i", ret);
> 
> I already answered this question in the previous review: "If
> pa_stream_writable_size() says that N bytes can be written, and then
> writing N bytes fails, we can just give up and goto fail."
Now I quit here if ret != 0.
> > +                    }
> > +                }
> > +            }
> > +        }
> > +    }
> > +fail:
> > +    /* If this was no regular exit from the loop we have to continue
> > +     * processing messages until we received PA_MESSAGE_SHUTDOWN
> > +     *
> > +     * Note: is this a race condition? When a PA_MESSAGE_SHUTDOWN already within the queue?
> 
> I already answered this question in the previous review. No, it's not a
> race condition. This code will work just fine if PA_MESSAGE_SHUTDOWN has
> been sent. If PA_MESSAGE_SHUTDOWN has been sent, it means that
> pa__done() is being run from pa_module_unload(), and in that case the
> PA_CORE_MESSAGE_UNLOAD_MODULE handler won't do anything, because the
> module was already removed from core->modules before pa_module_unload()
> was called.
forgot to remove this coment.
> > +
> > +static void stream_state_callback(pa_stream *stream, void *userdata) {
> > +    struct userdata *u = userdata;
> > +
> > +    pa_assert(u);
> > +
> > +    switch (pa_stream_get_state(stream)) {
> > +        case PA_STREAM_FAILED:
> > +            pa_log_error("Stream failed.");
> > +            u->connected = false;
> > +            u->thread_mainloop_api->quit(u->thread_mainloop_api, TUNNEL_THREAD_FAILED_MAINLOOP);
> > +            break;
> > +        case PA_STREAM_TERMINATED:
> > +            pa_log_debug("Stream terminated.");
> > +            break;
> > +        default:
> > +            break;
> > +    }
> 
> When the stream becomes ready, you should check what the actual tlength
> is, and set the sink latency accordingly. Also, if the server changes
> the tlength at runtime, the sink latency needs to be updated also in
> that situation. You can use pa_stream_set_buffer_attr_callback() for
> getting notifications about changes in the buffer attributes.
> > +}
> > +
> > +static void context_state_callback(pa_context *c, void *userdata) {
> > +    struct userdata *u = userdata;
> > +    int c_errno;
> > +
> > +    pa_assert(u);
> > +
> > +    switch (pa_context_get_state(c)) {
> > +        case PA_CONTEXT_UNCONNECTED:
> > +        case PA_CONTEXT_CONNECTING:
> > +        case PA_CONTEXT_AUTHORIZING:
> > +        case PA_CONTEXT_SETTING_NAME:
> > +            break;
> > +        case PA_CONTEXT_READY: {
> > +            pa_proplist *proplist;
> > +            const char *username = pa_get_user_name_malloc();
> > +            const char *hostname = pa_get_host_name_malloc();
> > +            /* TODO: old tunnel say 'Null-Output' */
> 
> What do you mean by this comment?
That the title/description is fetched from the remote side. I have to update this after the stream is ready.
> > +            char *stream_name = pa_sprintf_malloc("%s for %s@%s", "Tunnel", username, hostname);
> 
> Why is "Tunnel" inserted to the string via substitution instead of
> directly?
> 
> Also, I think the string should be translatable.
Because I should fix 
> > +
> > +            pa_log_debug("Connection successful. Creating stream.");
> > +            pa_assert(!u->stream);
> > +
> > +            proplist = tunnel_new_proplist(u);
> > +            pa_proplist_sets(proplist, PA_PROP_MEDIA_ROLE, "sound");
> 
> "sound" is not a standard media role, and therefore it's not useful.
> It's probably best to not set any role, because we don't know what the
> tunnel will be used for. (The old tunnel module sets role "abstract",
> but I don't think that's really useful.)
but abstract wasn't documented. Should I update the documentation?
> 
> > +            pa_assert(proplist);
> 
> If you want to have this assertion, it should be before
> pa_proplist_sets().
I moved it into tunnel_new_proplist
> > +
> > +            u->stream = pa_stream_new_with_proplist(u->context,
> > +                                                    stream_name,
> > +                                                    &u->sink->sample_spec,
> > +                                                    &u->sink->channel_map,
> > +                                                    proplist);
> > +            pa_proplist_free(proplist);
> > +            pa_xfree(stream_name);
> > +
> > +            if(!u->stream) {
> 
> Missing space after "if".
> 
> > +                pa_log_error("Could not create a stream.");
> > +                u->thread_mainloop_api->quit(u->thread_mainloop_api, TUNNEL_THREAD_FAILED_MAINLOOP);
> > +                return;
> > +            }
> > +
> > +
> 
> Extra empty line.
> 
> > +            pa_context_subscribe(u->context, PA_SUBSCRIPTION_MASK_SINK_INPUT, NULL, NULL);
> 
> Subscribing without providing a callback is pointless. Also, you don't
> unref the pa_operation object that the function returns.
oops :).
> > +
> > +            pa_stream_set_state_callback(u->stream, stream_state_callback, userdata);
> > +            if (pa_stream_connect_playback(u->stream,
> > +                                           u->remote_sink_name,
> > +                                           &u->bufferattr,
> > +                                           PA_STREAM_START_CORKED | PA_STREAM_AUTO_TIMING_UPDATE,
> 
> I asked you to add DONT_MOVE to the flags.
> 
> Also, I now realized that PA_STREAM_INTERPOLATE_TIMING should be added
> to the flags too.
Why do I really need DONT_MOVE? I like to move the streams on the remove side.
> > +                                           NULL,
> > +                                           NULL) < 0) {
> > +                /* TODO fail */
> 
> Why is this not implemented? It should be a simple case of logging an
> error and calling quit() on the mainloop.
fixed
> > +            }
> > +            u->connected = true;
> > +            break;
> > +        }
> > +        case PA_CONTEXT_FAILED:
> > +            c_errno = pa_context_errno(u->context);
> > +            pa_log_debug("Context failed with err %d.", c_errno);
> 
> pa_strerror() is more informative than the plain error code.
> 
> > +            u->connected = false;
> > +            u->thread_mainloop_api->quit(u->thread_mainloop_api, TUNNEL_THREAD_FAILED_MAINLOOP);
> 
> There could be a "quit" section, so that you could write just "goto
> quit;" instead of setting connected = false and calling quit() multiple
> times in this function.
I'm not sure if I really need connected anymore.
Will be refactored.

> > +
> > +    nbytes = pa_usec_to_bytes(block_usec, &s->sample_spec);
> > +    pa_sink_set_max_rewind_within_thread(s, nbytes);
> 
> max_rewind should be 0, since you don't support rewinding.
ok.
> > +    pa_sink_set_max_request_within_thread(s, nbytes);
> > +
> > +    if (block_usec != (pa_usec_t) -1) {
> > +        u->bufferattr.tlength = nbytes;
> 
> block_usec is never -1, because if
> pa_sink_get_requested_latency_within_thread() returns -1, then you set
> block_usec to s->thread_info.max_latency.
> 
> To be consistent with the latency all the time, I think you should set
> the initial tlength to s->thread_info.max_latency too, instead of
> initializing it to -1 like you do now.
> > +    }
> 
> Unnecessary braces.
> 
> > +
> > +    if (u->stream && PA_STREAM_IS_GOOD(pa_stream_get_state(u->stream))) {
> > +        pa_stream_set_buffer_attr(u->stream, &u->bufferattr, NULL, NULL);
> 
> You don't unref the pa_operation object that the function returns.
> 
> It's a bit hairy issue what to do if the stream is not yet ready when
> the sink requested latency changes. You use PA_STREAM_IS_GOOD() here,
> but it seems that pa_stream_set_buffer_attr() fails if the state is not
> READY. PA_STREAM_IS_GOOD(), on the other hand, returns true also if the
> state is CREATING. So what to do? I think it would best to postpone the
> pa_stream_set_buffer_attr() call until the stream becomes ready. When
> the stream becomes ready, check the requested latency, and if it's
> something else than max_latency or the tlength that the server assigned
> to the stream, call pa_stream_set_buffer_attr().
> 
> > +    }
> 
> Unnecessary braces.
> 
> > +}
> > +
> > +static int sink_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
> > +    struct userdata *u = PA_SINK(o)->userdata;
> > +
> > +    switch (code) {
> > +        case PA_SINK_MESSAGE_GET_LATENCY: {
> > +            int negative;
> > +            pa_usec_t remote_latency;
> > +
> > +            if (!PA_SINK_IS_LINKED(u->sink->thread_info.state)) {
> > +                *((pa_usec_t*) data) = 0;
> > +                return 0;
> > +            }
> > +
> > +            if (!u->stream) {
> > +                *((pa_usec_t*) data) = 0;
> > +                return 0;
> > +            }
> > +
> > +            if (!PA_STREAM_IS_GOOD(pa_stream_get_state(u->stream))) {
> 
> You should check for PA_STREAM_READY instead of PA_STREAM_IS_GOOD, or
> alternatively not have have this check at all and let
> pa_stream_get_latency() return an error when the stream state is
> something else than READY.
> 
> > +                *((pa_usec_t*) data) = 0;
> > +                return 0;
> > +            }
> > +
> > +            if (pa_stream_get_latency(u->stream, &remote_latency, &negative) < 0) {
> > +                *((pa_usec_t*) data) = 0;
> > +                return 0;
> > +            }
> > +
> > +            *((pa_usec_t*) data) =
> > +                /* Add the latency from libpulse */
> > +                remote_latency;
> > +                /* do we have to add more latency here ? */
> 
> I already answered this question in the previous review. You don't need
> to add more latency.
removed
> > +            return 0;
> > +        }
> > +    }
> > +    return pa_sink_process_msg(o, code, data, offset, chunk);
> > +}
> > +
> > +int pa__init(pa_module *m) {
> > +    struct userdata *u = NULL;
> > +    pa_modargs *ma = NULL;
> > +    pa_sink_new_data sink_data;
> > +    pa_sample_spec ss;
> > +    pa_channel_map map;
> > +    const char *remote_server = NULL;
> > +    const char *sink_name = NULL;
> > +    char *default_sink_name = NULL;
> > +
> > +    pa_assert(m);
> > +
> > +    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
> > +        pa_log("Failed to parse module arguments.");
> > +        goto fail;
> > +    }
> > +
> > +    ss = m->core->default_sample_spec;
> > +    map = m->core->default_channel_map;
> > +    if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) {
> > +        pa_log("Invalid sample format specification or channel map");
> > +        goto fail;
> > +    }
> > +
> > +    remote_server = pa_modargs_get_value(ma, "server", NULL);
> > +    if (!remote_server) {
> > +        pa_log("No server given!");
> > +        goto fail;
> > +    }
> > +
> > +    u = pa_xnew0(struct userdata, 1);
> > +    u->module = m;
> > +    m->userdata = u;
> > +    u->remote_server = pa_xstrdup(remote_server);
> > +    u->thread_mainloop = pa_mainloop_new();
> > +    if (u->thread_mainloop == NULL) {
> > +        pa_log("Failed to create mainloop");
> > +        goto fail;
> > +    }
> > +    u->thread_mainloop_api = pa_mainloop_get_api(u->thread_mainloop);
> > +
> > +    u->remote_sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL));
> > +
> > +    u->bufferattr.maxlength = (uint32_t) -1;
> > +    u->bufferattr.minreq = (uint32_t) -1;
> > +    u->bufferattr.prebuf = (uint32_t) -1;
> > +    u->bufferattr.tlength = (uint32_t) -1;
> > +
> > +    pa_thread_mq_init_thread_mainloop(&u->thread_mq, m->core->mainloop, pa_mainloop_get_api(u->thread_mainloop));
> > +
> > +    /* Create sink */
> > +    pa_sink_new_data_init(&sink_data);
> > +    sink_data.driver = __FILE__;
> > +    sink_data.module = m;
> > +
> > +    default_sink_name = pa_sprintf_malloc("tunnel-sink-new.%s", remote_server);
> 
> I would prefer "tunnel-sink" as the prefix instead of "tunnel-sink-new".
> The reason is that the module name is supposed to be
> "module-tunnel-sink-new" only temporarily, until it's ready to replace
> the original module. It's easy to forget to change this string when
> doing the module renaming, and I don't think it's important to see from
> the sink name which version of the module is being used.
I can not load the old tunnel as long the new is loaded when both have the same name.
> 
> > +    sink_name = pa_modargs_get_value(ma, "sink_name", default_sink_name);
> > +
> > +    pa_sink_new_data_set_name(&sink_data, sink_name);
> > +    pa_sink_new_data_set_sample_spec(&sink_data, &ss);
> > +    pa_sink_new_data_set_channel_map(&sink_data, &map);
> > +
> > +    pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "sound");
> > +    pa_proplist_setf(sink_data.proplist,
> > +                     PA_PROP_DEVICE_DESCRIPTION,
> > +                     _("Tunnel to %s/%s"),
> > +                     remote_server,
> > +                     pa_strempty(u->remote_sink_name));
> > +
> > +    if (pa_modargs_get_proplist(ma, "sink_properties", sink_data.proplist, PA_UPDATE_REPLACE) < 0) {
> > +        pa_log("Invalid properties");
> > +        pa_sink_new_data_done(&sink_data);
> > +        goto fail;
> > +    }
> > +    /* TODO: check PA_SINK_LATENCY + PA_SINK_DYNAMIC_LATENCY */
> 
> What do you mean by this comment?
removed. I tested it already.

I will look over all pa_operations if there are some left which needs unrefed. Tomorrow evening I should fixed all your suggestions and send an update to the list.

Best,
Alex
-- 
Alexander Couzens

mail: lynxis at fe80.eu
sip/jabber: lynxis at c-base.org
mobile: +4915123277221


[Index of Archives]     [Linux Audio Users]     [AMD Graphics]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux