[pulseaudio-discuss] flat volumes for privileged apps
Hi all, Now that we are talking about flat volumes again (but I don't want to hijack the other thread), I would like to present another alternative to fix the problems with flat-volumes. The idea is that all apps, by default, operate in non-flat volume mode. This means all volume control done from the app is relative to the master volume. Privileged apps can see flat-volumes and thus (indirectly) change the master volume. One such privileged app is the volume control applet but it could be possible to manually enable trusted apps (maybe with a switch in the volume control next to the app stream). I made a little hack to let you try this, gnome-control-center is a hardcoded privileged app but you can see how we can store that in the database later or how we can hook this into the security framework. http://cgit.freedesktop.org/~wtay/pulseaudio/commit/?h=flat-volume-privilege-hack&id=1b203fe6bcc8bba1db1911fd4dbf225f36a6dbb9 I like this idea because: 1) it does not need any new api or changes to apps 2) sets a default that will not cause 100% master volume with misbehaving apps 3) has the master/app volume separation that people understand and that is also exposed in apps (volume in totem, master in gnome-shell header). 4) still exposes the flat-volume model if needed, which is IMHO the only way to sanely increase the volume of just 1 single app (when it needs adjusting the master volume). 5) minimal code changes. What do you think? Wim ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
Re: [pulseaudio-discuss] flat volumes for privileged apps
Removing the flat volume from pulseaudio and moving it into the mixer would make it hard to impossible to implement the following items: 1) give some apps flat volumes and others not. 2) raise the volume of just one stream in the mixer without affecting others. Since the mixer is not running inside pulseaudio, it can't atomically update both the master volume and the volumes of all other streams Wim On 19 October 2015 at 17:32, Alexander E. Patrakov wrote: > 19.10.2015 19:38, Wim Taymans wrote: > >> Hi all, >> >> Now that we are talking about flat volumes again (but I don't want to >> hijack the other thread), I would like to present another alternative to >> fix >> the problems with flat-volumes. >> >> The idea is that all apps, by default, operate in non-flat volume mode. >> This means all volume control done from the app is relative to the master >> volume. >> >> Privileged apps can see flat-volumes and thus (indirectly) change the >> master volume. One such privileged app is the volume control applet but >> it could be possible to manually enable trusted apps (maybe with a switch >> in the volume control next to the app stream). >> >> I made a little hack to let you try this, gnome-control-center is a >> hardcoded >> privileged app but you can see how we can store that in the database later >> or how we can hook this into the security framework. >> >> >> http://cgit.freedesktop.org/~wtay/pulseaudio/commit/?h=flat-volume-privilege-hack&id=1b203fe6bcc8bba1db1911fd4dbf225f36a6dbb9 >> >> I like this idea because: >> >> 1) it does not need any new api or changes to apps >> 2) sets a default that will not cause 100% master volume with misbehaving >> apps >> 3) has the master/app volume separation that people understand and that is >> also exposed in apps (volume in totem, master in gnome-shell header). >> 4) still exposes the flat-volume model if needed, which is IMHO the only >> way >> to sanely increase the volume of just 1 single app (when it needs >> adjusting >> the master volume). >> 5) minimal code changes. >> >> What do you think? >> > > I have looked into this patch idea, and I think that even more minimal > changes on pulseaudio side (but not minimal overall) are possible. However, > your approach has an advantage of actually having a patch :) > > Please note that, under your proposal, and also without any patch, any > application can introspect and set any sink volume directly, but no > application except dedicated mixer applications currently does this. > > So here is a strawman counterproposal that should have a similar effect. > Feel free to test whether the idea is implementable, and compare. > > 1. All apps operate with non-flat volumes. Thus, we don't have to > implement any policy in pulseaudio, and can even remove all the flat-volume > code. > > 2. Alter the logic for presenting sink input volumes to the user in > gnome-control-center and all other mixer applications. That is, it should > introspect both sink and sink input volumes, and move the slider to the > correct position according to the product of them. If a user moves the > slider, adjust the sink input volume if possible (i.e. if it is left of the > sink volume). If not, adjust both the sink volume and volumes of all sink > inputs connected there. > > I.e., move all flat volume logic from pulseaudio into mixer applications, > like it is done in Windows 7. Then we would have a very simple rule to > enforce if we want to implement privileged/unprivileged app separation: > "privileged app" = "has access to sink volumes". > > > Yes, I understand that this proposal looks at odds with my previous "ack" > to Arun's idea. That "ack" is still in force. > > -- > Alexander E. Patrakov > ___ > pulseaudio-discuss mailing list > pulseaudio-discuss@lists.freedesktop.org > http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss > ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
Re: [pulseaudio-discuss] flat volumes for privileged apps
On 20 October 2015 at 06:53, Arun Raghavan wrote: > On Mon, 2015-10-19 at 16:38 +0200, Wim Taymans wrote: > > I think this option is more or less the same as disabling flat volumes. > The user is back to having at least two actions to control volume > (either app slider and panel slider, or open panel and adjust app > slider). > It is, it would disable flat volumes by default. But it would allow you to enable them again for the streams you want. > > The point of flat volumes was that if you're using the application > volume slider, you don't need to hunt for two volume sliders to adjust > to get the full range of volume that your hardware allows. To my mind, > your proposal makes sense as an alternative to what I suggested only if > we move to a model where we suggests applications do _not_ have volume > sliders at all, and then design the desktop UX differently to provide a > single-action way to adjust volumes. > > As an example, the shell could track what the current playing stream is > based on the foreground application and volume controls (hardware and > panel mixer) could track that volume and apply changes as flat volumes. > So the user is always only dealing with one control, by default. (I > just thought of this, so probably needs to be fleshed out to make > sense, or maybe it doesn't at all.) > This is an interesting idea. Because the slider is part of the shell, we can trust it to be user-controlled and thus give is access to the flat-volumes of the stream. It's like the slider from the current app in the mixer appears in the shell task bar. In any case, this would work nicely with this proposal and since you would remove the volume from the apps, making all app-controlled volumes relative would be a good default. Wim > > -- Arun > ___ > pulseaudio-discuss mailing list > pulseaudio-discuss@lists.freedesktop.org > http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss > ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
[pulseaudio-discuss] Access control
Hi guys, I'm having another look at the access control patches. I revived my old patches and found some trouble with the async stuff that I fixed here: https://cgit.freedesktop.org/~wtay/pulseaudio/log/?h=access-hooks There is also an example on how to start and complete an async access check for starting a recording. I believe Ahmed Darwish is building on top of that so it might be useful to get it working. Now I'm taking a look at the info in pa_client that is available to decide what access checks we need to do for each client. Ideally we would need the pid of the process with we can currently find in the pa_proplist of the client. Unfortunately this pid is whatever the client sends us in a proplist in the set_client_name command so we need something more secure. We do send the pid and gid with the SCM_CREDENTIALS ancillary data in the AUTH command. Since the kernel checks things, we can be guaranteed that when we get the credentials, they are correct. What I would like to do is make these credentials available somewhere. I would like to make a new key in the client proplist with the verified pid from the credentials but the problem is that we then need to make sure that a set_client_name command can't overwrite the value, which involves some filtering or keys. Alternatively we could make a new pa_client field to store the verified pid and gid.. Does this sound better or worse? Wim ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
Re: [pulseaudio-discuss] Access control
On 15 July 2016 at 11:14, David Henningsson wrote: > > > On 2016-07-15 11:05, Wim Taymans wrote: > >> Hi guys, >> >> I'm having another look at the access control patches. I revived my old >> patches and found some trouble with the async stuff that I fixed here: >> >> https://cgit.freedesktop.org/~wtay/pulseaudio/log/?h=access-hooks < >> https://cgit.freedesktop.org/%7Ewtay/pulseaudio/log/?h=access-hooks> >> > > FWIW, I also remember fixing a bug or two before I added your patches to > Ubuntu. The way it looks in Ubuntu now is here: > > > http://anonscm.debian.org/cgit/pkg-pulseaudio/pulseaudio.git/tree/debian/patches?h=ubuntu > > (see patches 0406, 0407 and 0408) > It looks ok, you fixed the length in _copy and the reading of the command and tag when resuming the async operation. Interestingly you added the pa_creds to the pa_client. Any reason not to upstream this? Wim ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
[pulseaudio-discuss] Access control
Hi All, I took another look at the access control patches. There was a desire from Arun and Tanu to have the access control checks more integrated into the core objects instead of just some checks in the native protocol. I was a bit reluctant to start this because it would involve passing around a lot of pa_client objects to many functions in order to do access control. This week, I put my brain to 0 and started this anyway, You can find the result in this branch: (1) https://cgit.freedesktop.org/~wtay/pulseaudio/log/?h=access-hooks-arg I then spent some time thinking about using TLS to pass the client around, more like a context to evaluate the functions in than an argument of the function. Tanu then suggested to put the current_client in the pa_core object where it can be picked up from anywhere. The trick is then to set and clear the current_client in the various protocols. The result can be seen here: (2) https://cgit.freedesktop.org/~wtay/pulseaudio/log/?h=access-hooks-core You can still find the old way here: (3) https://cgit.freedesktop.org/~wtay/pulseaudio/log/?h=access-hooks I think (1) is quite ugly and requires you to modify many functions with a new parameter that is not at all related to what the function does. It does however give you good control over what you can check. One example is pa_sink_update_rate() which might suspend and unsuspend the source to implement the rate change. It a new client does not have permission to suspend a sink, it will not be able to change the rate. (2) looks quite ok. Setting and clearing the current_client is straightforward and easily verifiable. You can also be certain that when you add new checks, you can just get the client from there instead of having to change a method. The downside is that if ever a non-main thread tries to do some access control, you get random behaviour. using TLS will, of course fix this. (3) is probably still the most simple solution but only deals with the native-protocol. (1) and (2) now also automatically work for the cli, DBus, esound, http, simple and native protocol. What do you think? Which option do you like best? Wim ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
Re: [pulseaudio-discuss] Access control
On 13 February 2017 at 13:58, Tanu Kaskinen wrote: > On Fri, 2017-01-27 at 17:15 +0100, Wim Taymans wrote: >> Hi All, >> >> I think (1) is quite ugly and requires you to modify many functions >> with a new parameter that >> is not at all related to what the function does. > > I disagree with the characterization that the client parameter is not > related to what the function does. If the function does access control, > then the client parameter is very much related to that. Together with things such as logging,configuration and allocation, security is typically a Cross-cutting concern (https://en.wikipedia.org/wiki/Cross-cutting_concern). The methods that operate on objects should not include the parameters for the cross-cutting concern, you would typically keep those stored in a global environment or singleton (like how we have the logging in a singleton, the mempool and configuration in pa_core, ..). We should to the same with the current client, IMHO. > >> It does however give >> you good control over what >> you can check. One example is pa_sink_update_rate() which might >> suspend and unsuspend >> the source to implement the rate change. It a new client does not have >> permission to suspend a >> sink, it will not be able to change the rate. > > If I understood correctly, you're comparing options (1) and (2) here, > and you're saying that option (2) will prevent sink rate changes > for streams that are played by clients that don't have the permission > to suspend sinks. I don't think that's true. pa_sink_update_rate() can > set current_client to NULL before it calls pa_sink_suspend(). That is true but it is not so nice because it assumes that pa_sink_update_rate() knows pa_sink_suspend() is going to perform some sort of access control that would somehow fail and so it needs to work around it. Ideally pa_sink_suspend() knows in what context it's called and can decide this for itself. Fortunately pa_sink_suspend() has a 'cause' argument that we can actually use for this: PA_SUSPEND_USER. We are lucky here, theoretically, in some cases there might not be enough context to do the right access checks. > >> (2) looks quite ok. Setting and clearing the current_client is >> straightforward and easily verifiable. >> You can also be certain that when you add new checks, you can just get >> the client from there >> instead of having to change a method. The downside is that if ever a >> non-main thread tries to >> do some access control, you get random behaviour. using TLS will, of >> course fix this. > > I don't know why you're worried about other threads. I don't see the > need to do access control from other threads than the main thread. I've put some asserts to make sure the set and get are always done from the control thread. > >> (3) is probably still the most simple solution but only deals with the >> native-protocol. (1) and (2) >> now also automatically work for the cli, DBus, esound, http, simple >> and native protocol. >> >> What do you think? Which option do you like best? > > I feel I need to have a close look at all the implementations to give a > definite opinion. So far I've only looked at the implementation of (3). > At the moment I'm not strongly opposed to any of the alternatives, but > if I had to choose right now, I'd prefer (1), then (2) and last (3). > Option (3) is last, because it's easier to forget access checks when > the core doesn't enforce it. (1) and (2) are functionally equivalent. > Both are about enforcing access control in the core, and in both cases > the core functions need the client object as a parameter. Option (1) > does the parameter passing in the usual way, and option (2) does the > parameter passing in an unusual way. Option (1) makes it obvious which > functions involve access control, and forces the programmer to think > about how access control should be done in each case, which I believe > reduces the possibility for errors. > I've update the flatpak branch here: https://cgit.freedesktop.org/~wtay/pulseaudio/log/?h=flatpack Since option 3 is now at the bottom of the list and I also think it's inferior to the other options, I'm no longer going to update or propose it. I tried to incorporate the suggestions you listed below. > creds: add pid to pa_creds and use store it in pa_client > > > This patch seems to have three separate changes that should probably be > in separate patches: adding pid to pa_creds, adding creds to pa_client > and adding the CLIENT_AUTH hook. Split into 3 patches. > > +typedef struct pa_protocol_nat
[pulseaudio-discuss] [PATCH] combine-sinks: add suport for DYNAMIC_LATENCY
Mark the sink as DYNAMIC_LATENCY and implement update_sink_latency_range on its sink-input to collect the combined latency range of all sinks. Implement update_requested_latency on the sink to configure the final latency by combining the sink-input requested latencies. This makes us honour the client latency request. We don't need to call update_max_request() and update_fixed_latency() when adding and removing outputs, this is already done when we attach the sink-inputs. Also add more debug log. Fixes https://bugs.freedesktop.org/show_bug.cgi?id=47899 --- src/modules/module-combine-sink.c | 135 +++--- 1 file changed, 98 insertions(+), 37 deletions(-) diff --git a/src/modules/module-combine-sink.c b/src/modules/module-combine-sink.c index deabceb..d705542 100644 --- a/src/modules/module-combine-sink.c +++ b/src/modules/module-combine-sink.c @@ -104,7 +104,8 @@ struct output { /* For communication of the stream parameters to the sink thread */ pa_atomic_t max_request; -pa_atomic_t requested_latency; +pa_atomic_t max_latency; +pa_atomic_t min_latency; PA_LLIST_FIELDS(struct output); }; @@ -150,11 +151,12 @@ enum { SINK_MESSAGE_NEED, SINK_MESSAGE_UPDATE_LATENCY, SINK_MESSAGE_UPDATE_MAX_REQUEST, -SINK_MESSAGE_UPDATE_REQUESTED_LATENCY +SINK_MESSAGE_UPDATE_LATENCY_RANGE }; enum { SINK_INPUT_MESSAGE_POST = PA_SINK_INPUT_MESSAGE_MAX, +SINK_INPUT_MESSAGE_SET_REQUESTED_LATENCY }; static void output_disable(struct output *o); @@ -469,35 +471,44 @@ static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) { return; pa_atomic_store(&o->max_request, (int) nbytes); +pa_log_debug("Sink input update max request %lu", (unsigned long) nbytes); pa_asyncmsgq_post(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_UPDATE_MAX_REQUEST, NULL, 0, NULL, NULL); } /* Called from thread context */ -static void sink_input_update_sink_requested_latency_cb(pa_sink_input *i) { +static void sink_input_update_sink_latency_range_cb(pa_sink_input *i) { struct output *o; -pa_usec_t c; +pa_usec_t min, max, fix; pa_assert(i); pa_sink_input_assert_ref(i); pa_assert_se(o = i->userdata); -c = pa_sink_get_requested_latency_within_thread(i->sink); - -if (c == (pa_usec_t) -1) -c = i->sink->thread_info.max_latency; +fix = i->sink->thread_info.fixed_latency; +if (fix > 0) { + min = fix; + max = fix; +} else { + min = i->sink->thread_info.min_latency; + max = i->sink->thread_info.max_latency; +} -if (pa_atomic_load(&o->requested_latency) == (int) c) +if ((pa_atomic_load(&o->min_latency) == (int) min) && +(pa_atomic_load(&o->max_latency) == (int) max)) return; -pa_atomic_store(&o->requested_latency, (int) c); -pa_asyncmsgq_post(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_UPDATE_REQUESTED_LATENCY, NULL, 0, NULL, NULL); +pa_atomic_store(&o->min_latency, (int) min); +pa_atomic_store(&o->max_latency, (int) max); +pa_log_debug("Sink input update latency range %lu %lu", (unsigned long) min, (unsigned long) max); +pa_asyncmsgq_post(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_UPDATE_LATENCY_RANGE, NULL, 0, NULL, NULL); } /* Called from I/O thread context */ static void sink_input_attach_cb(pa_sink_input *i) { struct output *o; -pa_usec_t c; +pa_usec_t fix, min, max; +size_t nbytes; pa_sink_input_assert_ref(i); pa_assert_se(o = i->userdata); @@ -517,13 +528,23 @@ static void sink_input_attach_cb(pa_sink_input *i) { pa_sink_input_request_rewind(i, 0, false, true, true); -pa_atomic_store(&o->max_request, (int) pa_sink_input_get_max_request(i)); +nbytes = pa_sink_input_get_max_request(i); +pa_atomic_store(&o->max_request, (int) nbytes); +pa_log_debug("attach max request %lu", (unsigned long) nbytes); -c = pa_sink_get_requested_latency_within_thread(i->sink); -pa_atomic_store(&o->requested_latency, (int) (c == (pa_usec_t) -1 ? 0 : c)); +fix = i->sink->thread_info.fixed_latency; +if (fix > 0) { + min = max = fix; +} else { + min = i->sink->thread_info.min_latency; + max = i->sink->thread_info.max_latency; +} +pa_atomic_store(&o->min_latency, (int) min); +pa_atomic_store(&o->max_latency, (int) max); +pa_log_debug("attach latency range %lu %lu", (unsigned long) min, (unsigned long) max); pa_asyncmsgq_post(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_UPDATE_MAX_REQUEST, NULL, 0, NULL, NULL); -pa_asyncmsgq_post(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_UPDATE_REQUESTED_LATENCY, NULL, 0, NULL, NULL); +pa_asyncmsgq_post(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_UPDATE_LATENCY_RANGE, NULL, 0, NULL, NULL); } /* Called from I/O thread context */ @@ -580,6 +601,14 @@ static int sink_inpu
[pulseaudio-discuss] [PATCH 2/2] combine-sink: add suport for DYNAMIC_LATENCY
Mark the sink as DYNAMIC_LATENCY and implement update_sink_latency_range on its sink-input to collect the combined latency range of all sinks. Implement update_requested_latency on the sink to configure the final latency by combining the sink-input requested latencies. This makes us honour the client latency request. Also add more debug log. Fixes https://bugs.freedesktop.org/show_bug.cgi?id=47899 --- src/modules/module-combine-sink.c | 134 -- 1 file changed, 98 insertions(+), 36 deletions(-) diff --git a/src/modules/module-combine-sink.c b/src/modules/module-combine-sink.c index a454a21..ada5611 100644 --- a/src/modules/module-combine-sink.c +++ b/src/modules/module-combine-sink.c @@ -104,7 +104,8 @@ struct output { /* For communication of the stream parameters to the sink thread */ pa_atomic_t max_request; -pa_atomic_t requested_latency; +pa_atomic_t max_latency; +pa_atomic_t min_latency; PA_LLIST_FIELDS(struct output); }; @@ -150,11 +151,12 @@ enum { SINK_MESSAGE_NEED, SINK_MESSAGE_UPDATE_LATENCY, SINK_MESSAGE_UPDATE_MAX_REQUEST, -SINK_MESSAGE_UPDATE_REQUESTED_LATENCY +SINK_MESSAGE_UPDATE_LATENCY_RANGE }; enum { SINK_INPUT_MESSAGE_POST = PA_SINK_INPUT_MESSAGE_MAX, +SINK_INPUT_MESSAGE_SET_REQUESTED_LATENCY }; static void output_disable(struct output *o); @@ -469,35 +471,44 @@ static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) { return; pa_atomic_store(&o->max_request, (int) nbytes); +pa_log_debug("Sink input update max request %lu", (unsigned long) nbytes); pa_asyncmsgq_post(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_UPDATE_MAX_REQUEST, NULL, 0, NULL, NULL); } /* Called from thread context */ -static void sink_input_update_sink_requested_latency_cb(pa_sink_input *i) { +static void sink_input_update_sink_latency_range_cb(pa_sink_input *i) { struct output *o; -pa_usec_t c; +pa_usec_t min, max, fix; pa_assert(i); pa_sink_input_assert_ref(i); pa_assert_se(o = i->userdata); -c = pa_sink_get_requested_latency_within_thread(i->sink); - -if (c == (pa_usec_t) -1) -c = i->sink->thread_info.max_latency; +fix = i->sink->thread_info.fixed_latency; +if (fix > 0) { +min = fix; +max = fix; +} else { +min = i->sink->thread_info.min_latency; +max = i->sink->thread_info.max_latency; +} -if (pa_atomic_load(&o->requested_latency) == (int) c) +if ((pa_atomic_load(&o->min_latency) == (int) min) && +(pa_atomic_load(&o->max_latency) == (int) max)) return; -pa_atomic_store(&o->requested_latency, (int) c); -pa_asyncmsgq_post(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_UPDATE_REQUESTED_LATENCY, NULL, 0, NULL, NULL); +pa_atomic_store(&o->min_latency, (int) min); +pa_atomic_store(&o->max_latency, (int) max); +pa_log_debug("Sink input update latency range %lu %lu", (unsigned long) min, (unsigned long) max); +pa_asyncmsgq_post(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_UPDATE_LATENCY_RANGE, NULL, 0, NULL, NULL); } /* Called from I/O thread context */ static void sink_input_attach_cb(pa_sink_input *i) { struct output *o; -pa_usec_t c; +pa_usec_t fix, min, max; +size_t nbytes; pa_sink_input_assert_ref(i); pa_assert_se(o = i->userdata); @@ -517,10 +528,20 @@ static void sink_input_attach_cb(pa_sink_input *i) { pa_sink_input_request_rewind(i, 0, false, true, true); -pa_atomic_store(&o->max_request, (int) pa_sink_input_get_max_request(i)); +nbytes = pa_sink_input_get_max_request(i); +pa_atomic_store(&o->max_request, (int) nbytes); +pa_log_debug("attach max request %lu", (unsigned long) nbytes); -c = pa_sink_get_requested_latency_within_thread(i->sink); -pa_atomic_store(&o->requested_latency, (int) (c == (pa_usec_t) -1 ? 0 : c)); +fix = i->sink->thread_info.fixed_latency; +if (fix > 0) { +min = max = fix; +} else { +min = i->sink->thread_info.min_latency; +max = i->sink->thread_info.max_latency; +} +pa_atomic_store(&o->min_latency, (int) min); +pa_atomic_store(&o->max_latency, (int) max); +pa_log_debug("attach latency range %lu %lu", (unsigned long) min, (unsigned long) max); /* We register the output. That means that the sink will start to pass data to * this output. */ @@ -547,6 +568,7 @@ static void sink_input_detach_cb(pa_sink_input *i) { pa_rtpoll_item_free(o->outq_rtpoll_item_write); o->outq_rtpoll_item_write = NULL; } + } /* Called from main context */ @@ -585,6 +607,14 @@ static int sink_input_process_msg(pa_msgobject *obj, int code, void *data, int64 pa_memblockq_flush_write(o->memblockq, true); return 0; + +case SINK_INPUT_MESSAGE_SET_REQUESTED_LATENCY: { +pa_usec
[pulseaudio-discuss] [PATCH 0/2] combine-sink: add support for DYNAMIC_LATENCY
Here are the updated patches that add support for DYNAMIC_LATENCY in the combine-sink module. I've also include a small patch to rework the way outputs are added and removed, I think this is nicer and more symetric. Wim Taymans (2): combine-sink: rework output add/remove combine-sink: add support for DYNAMIC_LATENCY src/modules/module-combine-sink.c | 159 ++ 1 file changed, 108 insertions(+), 51 deletions(-) -- 1.9.3 ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
[pulseaudio-discuss] [PATCH 1/2] combine-sink: rework output add/remove
Add the output from its sink-input attached callback and remove it again from the detach callback. This simplifies some output_enable and we can also avoid posting 2 messages for the sink. --- src/modules/module-combine-sink.c | 27 +++ 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/modules/module-combine-sink.c b/src/modules/module-combine-sink.c index ccb2a49..a454a21 100644 --- a/src/modules/module-combine-sink.c +++ b/src/modules/module-combine-sink.c @@ -522,8 +522,9 @@ static void sink_input_attach_cb(pa_sink_input *i) { c = pa_sink_get_requested_latency_within_thread(i->sink); pa_atomic_store(&o->requested_latency, (int) (c == (pa_usec_t) -1 ? 0 : c)); -pa_asyncmsgq_post(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_UPDATE_MAX_REQUEST, NULL, 0, NULL, NULL); -pa_asyncmsgq_post(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_UPDATE_REQUESTED_LATENCY, NULL, 0, NULL, NULL); +/* We register the output. That means that the sink will start to pass data to + * this output. */ +pa_asyncmsgq_send(o->userdata->sink->asyncmsgq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_ADD_OUTPUT, o, 0, NULL); } /* Called from I/O thread context */ @@ -533,6 +534,10 @@ static void sink_input_detach_cb(pa_sink_input *i) { pa_sink_input_assert_ref(i); pa_assert_se(o = i->userdata); +/* We unregister the output. That means that the sink doesn't + * pass any further data to this output */ +pa_asyncmsgq_send(o->userdata->sink->asyncmsgq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_REMOVE_OUTPUT, o, 0, NULL); + if (o->inq_rtpoll_item_read) { pa_rtpoll_item_free(o->inq_rtpoll_item_read); o->inq_rtpoll_item_read = NULL; @@ -719,6 +724,7 @@ static void output_add_within_thread(struct output *o) { /* Called from thread context of the io thread */ static void output_remove_within_thread(struct output *o) { + pa_assert(o); pa_sink_assert_io_context(o->sink); @@ -965,18 +971,11 @@ static void output_enable(struct output *o) { if (output_create_sink_input(o) >= 0) { if (pa_sink_get_state(o->sink) != PA_SINK_INIT) { - -/* First we register the output. That means that the sink - * will start to pass data to this output. */ -pa_asyncmsgq_send(o->userdata->sink->asyncmsgq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_ADD_OUTPUT, o, 0, NULL); - -/* Then we enable the sink input. That means that the sink +/* Enable the sink input. That means that the sink * is now asked for new data. */ pa_sink_input_put(o->sink_input); -} else -/* Hmm the sink is not yet started, do things right here */ -output_add_within_thread(o); +} } o->ignore_state_change = false; @@ -989,14 +988,10 @@ static void output_disable(struct output *o) { if (!o->sink_input) return; -/* First we disable the sink input. That means that the sink is +/* We disable the sink input. That means that the sink is * not asked for new data anymore */ pa_sink_input_unlink(o->sink_input); -/* Then we unregister the output. That means that the sink doesn't - * pass any further data to this output */ -pa_asyncmsgq_send(o->userdata->sink->asyncmsgq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_REMOVE_OUTPUT, o, 0, NULL); - /* Now deallocate the stream */ pa_sink_input_unref(o->sink_input); o->sink_input = NULL; -- 1.9.3 ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
[pulseaudio-discuss] [PATCH] combine-sink: add suport for DYNAMIC_LATENCY
Mark the sink as DYNAMIC_LATENCY and implement update_sink_latency_range on its sink-input to collect the combined latency range of all sinks. Implement update_requested_latency on the sink to configure the final latency by combining the sink-input requested latencies. This makes us honour the client latency request. Also add more debug log. Fixes https://bugs.freedesktop.org/show_bug.cgi?id=47899 --- src/modules/module-combine-sink.c | 142 -- 1 file changed, 107 insertions(+), 35 deletions(-) diff --git a/src/modules/module-combine-sink.c b/src/modules/module-combine-sink.c index a454a21..fa9c90b 100644 --- a/src/modules/module-combine-sink.c +++ b/src/modules/module-combine-sink.c @@ -104,7 +104,8 @@ struct output { /* For communication of the stream parameters to the sink thread */ pa_atomic_t max_request; -pa_atomic_t requested_latency; +pa_atomic_t max_latency; +pa_atomic_t min_latency; PA_LLIST_FIELDS(struct output); }; @@ -131,6 +132,8 @@ struct userdata { pa_resample_method_t resample_method; pa_usec_t block_usec; +pa_usec_t default_min_latency; +pa_usec_t default_max_latency; pa_idxset* outputs; /* managed in main context */ @@ -150,11 +153,12 @@ enum { SINK_MESSAGE_NEED, SINK_MESSAGE_UPDATE_LATENCY, SINK_MESSAGE_UPDATE_MAX_REQUEST, -SINK_MESSAGE_UPDATE_REQUESTED_LATENCY +SINK_MESSAGE_UPDATE_LATENCY_RANGE }; enum { SINK_INPUT_MESSAGE_POST = PA_SINK_INPUT_MESSAGE_MAX, +SINK_INPUT_MESSAGE_SET_REQUESTED_LATENCY }; static void output_disable(struct output *o); @@ -469,35 +473,44 @@ static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) { return; pa_atomic_store(&o->max_request, (int) nbytes); +pa_log_debug("Sink input update max request %lu", (unsigned long) nbytes); pa_asyncmsgq_post(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_UPDATE_MAX_REQUEST, NULL, 0, NULL, NULL); } /* Called from thread context */ -static void sink_input_update_sink_requested_latency_cb(pa_sink_input *i) { +static void sink_input_update_sink_latency_range_cb(pa_sink_input *i) { struct output *o; -pa_usec_t c; +pa_usec_t min, max, fix; pa_assert(i); pa_sink_input_assert_ref(i); pa_assert_se(o = i->userdata); -c = pa_sink_get_requested_latency_within_thread(i->sink); - -if (c == (pa_usec_t) -1) -c = i->sink->thread_info.max_latency; +fix = i->sink->thread_info.fixed_latency; +if (fix > 0) { +min = fix; +max = fix; +} else { +min = i->sink->thread_info.min_latency; +max = i->sink->thread_info.max_latency; +} -if (pa_atomic_load(&o->requested_latency) == (int) c) +if ((pa_atomic_load(&o->min_latency) == (int) min) && +(pa_atomic_load(&o->max_latency) == (int) max)) return; -pa_atomic_store(&o->requested_latency, (int) c); -pa_asyncmsgq_post(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_UPDATE_REQUESTED_LATENCY, NULL, 0, NULL, NULL); +pa_atomic_store(&o->min_latency, (int) min); +pa_atomic_store(&o->max_latency, (int) max); +pa_log_debug("Sink input update latency range %lu %lu", (unsigned long) min, (unsigned long) max); +pa_asyncmsgq_post(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_UPDATE_LATENCY_RANGE, NULL, 0, NULL, NULL); } /* Called from I/O thread context */ static void sink_input_attach_cb(pa_sink_input *i) { struct output *o; -pa_usec_t c; +pa_usec_t fix, min, max; +size_t nbytes; pa_sink_input_assert_ref(i); pa_assert_se(o = i->userdata); @@ -517,10 +530,20 @@ static void sink_input_attach_cb(pa_sink_input *i) { pa_sink_input_request_rewind(i, 0, false, true, true); -pa_atomic_store(&o->max_request, (int) pa_sink_input_get_max_request(i)); +nbytes = pa_sink_input_get_max_request(i); +pa_atomic_store(&o->max_request, (int) nbytes); +pa_log_debug("attach max request %lu", (unsigned long) nbytes); -c = pa_sink_get_requested_latency_within_thread(i->sink); -pa_atomic_store(&o->requested_latency, (int) (c == (pa_usec_t) -1 ? 0 : c)); +fix = i->sink->thread_info.fixed_latency; +if (fix > 0) { +min = max = fix; +} else { +min = i->sink->thread_info.min_latency; +max = i->sink->thread_info.max_latency; +} +pa_atomic_store(&o->min_latency, (int) min); +pa_atomic_store(&o->max_latency, (int) max); +pa_log_debug("attach latency range %lu %lu", (unsigned long) min, (unsigned long) max); /* We register the output. That means that the sink will start to pass data to * this output. */ @@ -547,6 +570,7 @@ static void sink_input_detach_cb(pa_sink_input *i) { pa_rtpoll_item_free(o->outq_rtpoll_item_write); o->outq_rtpoll_item_write = NULL; } + } /* Called from main context */ @@ -585,6 +609,14 @@ static i
[pulseaudio-discuss] [PATCH] combine-sink: add support for DYNAMIC_LATENCY
Here is an updated patch that implements DYNAMIC_LATENCY in the combine module. As suggested, the min latency is now set to 0 (or 500, it's clamped internally) when no output is connected. Max latency is always capped to 200ms (or min_latency, if it is larger). When no latency is requested from the sink-inputs, we configure ourselves for our calculated max_latency. Inspired by the null-sink, we use the configured latency to make sure that the null-renderer is woken up often enough. Wim Taymans (1): combine-sink: add suport for DYNAMIC_LATENCY src/modules/module-combine-sink.c | 142 -- 1 file changed, 107 insertions(+), 35 deletions(-) -- 1.9.3 ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
Re: [pulseaudio-discuss] ofono/HFP patches
You can get that eror when there is a problem with fd-passing over DBus. Anything special with your setup? SELinux, for example, did the same thing to me. See if sudo setenforce 0 makes it work... Wim On 11 August 2014 15:07, Tanu Kaskinen wrote: > On Fri, 2014-08-08 at 13:09 +0300, Tanu Kaskinen wrote: > > On Fri, 2014-08-08 at 12:47 +0300, Von Dentz, Luiz wrote: > > > Ive just return from vacation so it will take a few days to catch up, > > > if you fill that you can do it quickly please go ahead. > > > > "Quickly" is a relative term... I should have time to do it during next > > week. > > BlueZ doesn't seem to work on my machine (see [1]), so I can't do any > testing, and I don't want to write untested patches. > > [1] http://marc.info/?l=linux-bluetooth&m=140776166414824&w=2 > > -- > Tanu > > ___ > pulseaudio-discuss mailing list > pulseaudio-discuss@lists.freedesktop.org > http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss > ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
[pulseaudio-discuss] [PATCH 3/4] bluez5-util: always DISCONNECT on free
We always disconnect before free, move the state changed function in the free method. --- src/modules/bluetooth/bluez5-util.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c index b5c0726..777aca9 100644 --- a/src/modules/bluetooth/bluez5-util.c +++ b/src/modules/bluetooth/bluez5-util.c @@ -197,6 +197,8 @@ void pa_bluetooth_transport_put(pa_bluetooth_transport *t) { void pa_bluetooth_transport_free(pa_bluetooth_transport *t) { pa_assert(t); +transport_state_changed(t, PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED); + if (t->dispose) t->dispose (t); pa_hashmap_remove(t->device->discovery->transports, t->path); @@ -414,7 +416,6 @@ static void device_free(pa_bluetooth_device *d) { if (!(t = d->transports[i])) continue; -transport_state_changed(t, PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED); pa_bluetooth_transport_free(t); } @@ -1435,7 +1436,6 @@ static DBusMessage *endpoint_clear_configuration(DBusConnection *conn, DBusMessa if ((t = pa_hashmap_get(y->transports, path))) { pa_log_debug("Clearing transport %s profile %s", t->path, pa_bluetooth_profile_to_string(t->profile)); -transport_state_changed(t, PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED); pa_bluetooth_transport_free(t); } -- 1.9.3 ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
[pulseaudio-discuss] [PATCH 2/4] bluez5-util: add dispose function
Add a dispose function to the transport that is called before freeing the transport. Useful for cleaning up extra userdata. --- src/modules/bluetooth/bluez5-util.c | 2 ++ src/modules/bluetooth/bluez5-util.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c index 93677b4..b5c0726 100644 --- a/src/modules/bluetooth/bluez5-util.c +++ b/src/modules/bluetooth/bluez5-util.c @@ -197,6 +197,8 @@ void pa_bluetooth_transport_put(pa_bluetooth_transport *t) { void pa_bluetooth_transport_free(pa_bluetooth_transport *t) { pa_assert(t); +if (t->dispose) +t->dispose (t); pa_hashmap_remove(t->device->discovery->transports, t->path); pa_xfree(t->owner); pa_xfree(t->path); diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h index 67377e9..1c64d72 100644 --- a/src/modules/bluetooth/bluez5-util.h +++ b/src/modules/bluetooth/bluez5-util.h @@ -60,6 +60,7 @@ typedef enum pa_bluetooth_transport_state { typedef int (*pa_bluetooth_transport_acquire_cb)(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu); typedef void (*pa_bluetooth_transport_release_cb)(pa_bluetooth_transport *t); +typedef void (*pa_bluetooth_transport_dispose_cb)(pa_bluetooth_transport *t); struct pa_bluetooth_transport { pa_bluetooth_device *device; @@ -76,6 +77,7 @@ struct pa_bluetooth_transport { pa_bluetooth_transport_acquire_cb acquire; pa_bluetooth_transport_release_cb release; +pa_bluetooth_transport_dispose_cb dispose; void *userdata; }; -- 1.9.3 ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
[pulseaudio-discuss] [PATCH 4/4] backend-null: add HSP profile support
Add simple support for the HSP profile. This allows pulseaudio to output audio to a Headset using the HSP profile. Make the null backend the default, now that it does something. --- configure.ac | 4 +- src/modules/bluetooth/backend-null.c | 457 ++- 2 files changed, 455 insertions(+), 6 deletions(-) diff --git a/configure.ac b/configure.ac index 7b56210..6eb956e 100644 --- a/configure.ac +++ b/configure.ac @@ -1030,9 +1030,9 @@ AM_CONDITIONAL([HAVE_BLUEZ], [test "x$HAVE_BLUEZ" = x1]) ## Bluetooth Headset profiles backend ## AC_ARG_WITH(bluetooth_headset_backend, -AS_HELP_STRING([--with-bluetooth-headset-backend=],[Backend for Bluetooth headset profiles (ofono)])) +AS_HELP_STRING([--with-bluetooth-headset-backend=],[Backend for Bluetooth headset profiles (null)])) if test -z "$with_bluetooth_headset_backend" ; then -BLUETOOTH_HEADSET_BACKEND=ofono +BLUETOOTH_HEADSET_BACKEND=null else BLUETOOTH_HEADSET_BACKEND=$with_bluetooth_headset_backend fi diff --git a/src/modules/bluetooth/backend-null.c b/src/modules/bluetooth/backend-null.c index f8a145b..e3fe215 100644 --- a/src/modules/bluetooth/backend-null.c +++ b/src/modules/bluetooth/backend-null.c @@ -23,15 +23,464 @@ #include #endif +#include +#include +#include +#include #include +#include +#include +#include + +#include +#include + #include "bluez5-util.h" +struct pa_bluetooth_backend { + pa_core *core; + pa_dbus_connection *connection; + pa_bluetooth_discovery *discovery; + + PA_LLIST_HEAD(pa_dbus_pending, pending); +}; + +struct transport_rfcomm { +int rfcomm_fd; +pa_io_event *rfcomm_io; +pa_bluetooth_backend *backend; +}; + +#define BLUEZ_SERVICE "org.bluez" +#define BLUEZ_MEDIA_TRANSPORT_INTERFACE BLUEZ_SERVICE ".MediaTransport1" + +#define BLUEZ_ERROR_NOT_SUPPORTED "org.bluez.Error.NotSupported" + +#define BLUEZ_PROFILE_MANAGER_INTERFACE BLUEZ_SERVICE ".ProfileManager1" +#define BLUEZ_PROFILE_INTERFACE BLUEZ_SERVICE ".Profile1" + +#define HSP_AG_PROFILE "/Profile/HSPAGProfile" + +#define PROFILE_INTROSPECT_XML \ +DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ +""\ +" " \ +" " \ +" " \ +" "\ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" "\ +" " \ +" " \ +" " \ +"" + +static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_backend *backend, DBusMessage *m, +DBusPendingCallNotifyFunction func, void *call_data) { +pa_dbus_pending *p; +DBusPendingCall *call; + +pa_assert(backend); +pa_assert(m); + + pa_assert_se(dbus_connection_send_with_reply(pa_dbus_connection_get(backend->connection), m, &call, -1)); + +p = pa_dbus_pending_new(pa_dbus_connection_get(backend->connection), m, call, backend, call_data); +PA_LLIST_PREPEND(pa_dbus_pending, backend->pending, p); +dbus_pending_call_set_notify(call, func, p, NULL); + +return p; +} + +static int bluez5_sco_acquire_cb(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu) { +pa_bluetooth_device *d = t->device; +struct sockaddr_sco addr; +int err, i; +int sock; +bdaddr_t src; +bdaddr_t dst; +int voice = 0x60; +const char *src_addr, *dst_addr; + +src_addr = d->adapter->address; +dst_addr = d->address; + +for (i = 5; i >= 0; i--, src_addr += 3) +src.b[i] = strtol(src_addr, NULL, 16); +for (i = 5; i >= 0; i--, dst_addr += 3) +dst.b[i] = strtol(dst_addr, NULL, 16); + +sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO); +if (sock < 0) { +pa_log_error("socket(SEQPACKET, SCO) %s", pa_cstrerror(errno)); +return -1; +} + +memset(&addr, 0, sizeof(addr)); +addr.sco_family = AF_BLUETOOTH; +bacpy(&addr.sco_bdaddr, &src); + +if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) { +pa_log_error("bind(): %s", pa_cstrerror(errno)); +goto fail_close; +} + +if (voice) { +struct bt_voice opts; + +/* SCO voice setting */ +memset(&opts, 0, sizeof(opts)); +
[pulseaudio-discuss] [PATCH 0/4] Add simple HSP support in null backend
This set of patches adds a simple handler for the HSP profile to bluez and makes a new card for each connected device. This makes it possible to send audio to a Headset. The first 3 patches are mostly some very small cleanups and little bits to implement the last patch. The patch also switches the default backend from ofono to null. Wim Taymans (4): bluez5-device: use get_profile_direction bluez5-util: add dispose function bluez5-util: always DISCONNECT on free backend-null: add HSP profile support configure.ac | 4 +- src/modules/bluetooth/backend-null.c | 457 ++- src/modules/bluetooth/bluez5-util.c | 6 +- src/modules/bluetooth/bluez5-util.h | 2 + src/modules/bluetooth/module-bluez5-device.c | 32 +- 5 files changed, 476 insertions(+), 25 deletions(-) -- 1.9.3 ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
[pulseaudio-discuss] [PATCH 1/4] bluez5-device: use get_profile_direction
Use the get_profile_direction() helper function to decide when to add a source and a sink instead of enumerating profiles. --- src/modules/bluetooth/module-bluez5-device.c | 32 +--- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c index fdf4078..49f4676 100644 --- a/src/modules/bluetooth/module-bluez5-device.c +++ b/src/modules/bluetooth/module-bluez5-device.c @@ -1210,6 +1210,19 @@ static int setup_transport(struct userdata *u) { } /* Run from main thread */ +static pa_direction_t get_profile_direction(pa_bluetooth_profile_t p) { +static const pa_direction_t profile_direction[] = { +[PA_BLUETOOTH_PROFILE_A2DP_SINK] = PA_DIRECTION_OUTPUT, +[PA_BLUETOOTH_PROFILE_A2DP_SOURCE] = PA_DIRECTION_INPUT, +[PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT, +[PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT, +[PA_BLUETOOTH_PROFILE_OFF] = 0 +}; + +return profile_direction[p]; +} + +/* Run from main thread */ static int init_profile(struct userdata *u) { int r = 0; pa_assert(u); @@ -1220,13 +1233,11 @@ static int init_profile(struct userdata *u) { pa_assert(u->transport); -if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT || -u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) +if (get_profile_direction (u->profile) & PA_DIRECTION_OUTPUT) if (add_sink(u) < 0) r = -1; -if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT || -u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) +if (get_profile_direction (u->profile) & PA_DIRECTION_INPUT) if (add_source(u) < 0) r = -1; @@ -1546,19 +1557,6 @@ static char *cleanup_name(const char *name) { } /* Run from main thread */ -static pa_direction_t get_profile_direction(pa_bluetooth_profile_t p) { -static const pa_direction_t profile_direction[] = { -[PA_BLUETOOTH_PROFILE_A2DP_SINK] = PA_DIRECTION_OUTPUT, -[PA_BLUETOOTH_PROFILE_A2DP_SOURCE] = PA_DIRECTION_INPUT, -[PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT, -[PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT, -[PA_BLUETOOTH_PROFILE_OFF] = 0 -}; - -return profile_direction[p]; -} - -/* Run from main thread */ static pa_available_t get_port_availability(struct userdata *u, pa_direction_t direction) { pa_available_t result = PA_AVAILABLE_NO; unsigned i; -- 1.9.3 ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
[pulseaudio-discuss] [PATCH 1/5] bluez5-device: use get_profile_direction
Use the get_profile_direction() helper function to decide when to add a source and a sink instead of enumerating profiles. --- src/modules/bluetooth/module-bluez5-device.c | 32 +--- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c index fdf4078..49f4676 100644 --- a/src/modules/bluetooth/module-bluez5-device.c +++ b/src/modules/bluetooth/module-bluez5-device.c @@ -1210,6 +1210,19 @@ static int setup_transport(struct userdata *u) { } /* Run from main thread */ +static pa_direction_t get_profile_direction(pa_bluetooth_profile_t p) { +static const pa_direction_t profile_direction[] = { +[PA_BLUETOOTH_PROFILE_A2DP_SINK] = PA_DIRECTION_OUTPUT, +[PA_BLUETOOTH_PROFILE_A2DP_SOURCE] = PA_DIRECTION_INPUT, +[PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT, +[PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT, +[PA_BLUETOOTH_PROFILE_OFF] = 0 +}; + +return profile_direction[p]; +} + +/* Run from main thread */ static int init_profile(struct userdata *u) { int r = 0; pa_assert(u); @@ -1220,13 +1233,11 @@ static int init_profile(struct userdata *u) { pa_assert(u->transport); -if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT || -u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) +if (get_profile_direction (u->profile) & PA_DIRECTION_OUTPUT) if (add_sink(u) < 0) r = -1; -if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT || -u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) +if (get_profile_direction (u->profile) & PA_DIRECTION_INPUT) if (add_source(u) < 0) r = -1; @@ -1546,19 +1557,6 @@ static char *cleanup_name(const char *name) { } /* Run from main thread */ -static pa_direction_t get_profile_direction(pa_bluetooth_profile_t p) { -static const pa_direction_t profile_direction[] = { -[PA_BLUETOOTH_PROFILE_A2DP_SINK] = PA_DIRECTION_OUTPUT, -[PA_BLUETOOTH_PROFILE_A2DP_SOURCE] = PA_DIRECTION_INPUT, -[PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT, -[PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT, -[PA_BLUETOOTH_PROFILE_OFF] = 0 -}; - -return profile_direction[p]; -} - -/* Run from main thread */ static pa_available_t get_port_availability(struct userdata *u, pa_direction_t direction) { pa_available_t result = PA_AVAILABLE_NO; unsigned i; -- 1.9.3 ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
[pulseaudio-discuss] [PATCH 4/5] configure: fix headset check
--- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 7b56210..e1e2572 100644 --- a/configure.ac +++ b/configure.ac @@ -1037,7 +1037,7 @@ else BLUETOOTH_HEADSET_BACKEND=$with_bluetooth_headset_backend fi -AS_IF([test "x$BLUETOOTH_HEADSET_BACKEND" != "xofono && "test "x$BLUETOOTH_HEADSET_BACKEND" != "xnull"], +AS_IF([test "x$BLUETOOTH_HEADSET_BACKEND" != "xofono" && test "x$BLUETOOTH_HEADSET_BACKEND" != "xnull"], [AC_MSG_ERROR([*** Invalid Bluetooth Headset backend])]) AC_SUBST(BLUETOOTH_HEADSET_BACKEND) -- 1.9.3 ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
[pulseaudio-discuss] [PATCH 0/5] Add simple HSP support in new native backend
This set of patches adds a simple handler for the HSP profile to bluez and makes a new card for each connected device. This makes it possible to send audio to a Headset. The profile is implemented in a new native headset backend. The first 4 patches are mostly some very small cleanups and little bits to implement the last patch. The patch also switches the default backend from ofono to native. Wim Taymans (5): bluez5-device: use get_profile_direction bluez5-util: add dispose function bluez5-util: always DISCONNECT on free configure: fix headset check backend-native: add a new native headset backend configure.ac | 6 +- src/modules/bluetooth/backend-native.c | 474 +++ src/modules/bluetooth/bluez5-util.c | 6 +- src/modules/bluetooth/bluez5-util.h | 2 + src/modules/bluetooth/module-bluez5-device.c | 32 +- 5 files changed, 498 insertions(+), 22 deletions(-) create mode 100644 src/modules/bluetooth/backend-native.c -- 1.9.3 ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
[pulseaudio-discuss] [PATCH 2/5] bluez5-util: add dispose function
Add a dispose function to the transport that is called before freeing the transport. Useful for cleaning up extra userdata. --- src/modules/bluetooth/bluez5-util.c | 2 ++ src/modules/bluetooth/bluez5-util.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c index 93677b4..b5c0726 100644 --- a/src/modules/bluetooth/bluez5-util.c +++ b/src/modules/bluetooth/bluez5-util.c @@ -197,6 +197,8 @@ void pa_bluetooth_transport_put(pa_bluetooth_transport *t) { void pa_bluetooth_transport_free(pa_bluetooth_transport *t) { pa_assert(t); +if (t->dispose) +t->dispose (t); pa_hashmap_remove(t->device->discovery->transports, t->path); pa_xfree(t->owner); pa_xfree(t->path); diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h index 67377e9..1c64d72 100644 --- a/src/modules/bluetooth/bluez5-util.h +++ b/src/modules/bluetooth/bluez5-util.h @@ -60,6 +60,7 @@ typedef enum pa_bluetooth_transport_state { typedef int (*pa_bluetooth_transport_acquire_cb)(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu); typedef void (*pa_bluetooth_transport_release_cb)(pa_bluetooth_transport *t); +typedef void (*pa_bluetooth_transport_dispose_cb)(pa_bluetooth_transport *t); struct pa_bluetooth_transport { pa_bluetooth_device *device; @@ -76,6 +77,7 @@ struct pa_bluetooth_transport { pa_bluetooth_transport_acquire_cb acquire; pa_bluetooth_transport_release_cb release; +pa_bluetooth_transport_dispose_cb dispose; void *userdata; }; -- 1.9.3 ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
[pulseaudio-discuss] [PATCH 5/5] backend-native: add a new native headset backend
Add a simple native headset backend that implements support for the blutooth HSP profile. This allows pulseaudio to output audio to a Headset using the HSP profile. Make the native backend the default. --- configure.ac | 6 +- src/modules/bluetooth/backend-native.c | 474 + 2 files changed, 477 insertions(+), 3 deletions(-) create mode 100644 src/modules/bluetooth/backend-native.c diff --git a/configure.ac b/configure.ac index e1e2572..a91639f 100644 --- a/configure.ac +++ b/configure.ac @@ -1030,14 +1030,14 @@ AM_CONDITIONAL([HAVE_BLUEZ], [test "x$HAVE_BLUEZ" = x1]) ## Bluetooth Headset profiles backend ## AC_ARG_WITH(bluetooth_headset_backend, -AS_HELP_STRING([--with-bluetooth-headset-backend=],[Backend for Bluetooth headset profiles (ofono)])) + AS_HELP_STRING([--with-bluetooth-headset-backend=],[Backend for Bluetooth headset profiles (native)])) if test -z "$with_bluetooth_headset_backend" ; then -BLUETOOTH_HEADSET_BACKEND=ofono +BLUETOOTH_HEADSET_BACKEND=native else BLUETOOTH_HEADSET_BACKEND=$with_bluetooth_headset_backend fi -AS_IF([test "x$BLUETOOTH_HEADSET_BACKEND" != "xofono" && test "x$BLUETOOTH_HEADSET_BACKEND" != "xnull"], +AS_IF([test "x$BLUETOOTH_HEADSET_BACKEND" != "xofono" && test "x$BLUETOOTH_HEADSET_BACKEND" != "xnull" && test "x$BLUETOOTH_HEADSET_BACKEND" != "xnative"], [AC_MSG_ERROR([*** Invalid Bluetooth Headset backend])]) AC_SUBST(BLUETOOTH_HEADSET_BACKEND) diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c new file mode 100644 index 000..8af610c --- /dev/null +++ b/src/modules/bluetooth/backend-native.c @@ -0,0 +1,474 @@ +/*** + This file is part of PulseAudio. + + Copyright 2014 Wim Taymans + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "bluez5-util.h" + +struct pa_bluetooth_backend { + pa_core *core; + pa_dbus_connection *connection; + pa_bluetooth_discovery *discovery; + + PA_LLIST_HEAD(pa_dbus_pending, pending); +}; + +struct transport_rfcomm { +int rfcomm_fd; +pa_io_event *rfcomm_io; +pa_bluetooth_backend *backend; +}; + +#define BLUEZ_SERVICE "org.bluez" +#define BLUEZ_MEDIA_TRANSPORT_INTERFACE BLUEZ_SERVICE ".MediaTransport1" + +#define BLUEZ_ERROR_NOT_SUPPORTED "org.bluez.Error.NotSupported" + +#define BLUEZ_PROFILE_MANAGER_INTERFACE BLUEZ_SERVICE ".ProfileManager1" +#define BLUEZ_PROFILE_INTERFACE BLUEZ_SERVICE ".Profile1" + +#define HSP_AG_PROFILE "/Profile/HSPAGProfile" + +#define PROFILE_INTROSPECT_XML \ +DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ +""\ +" " \ +" " \ +" " \ +" "\ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" "\ +" " \ +" " \ +" " \ +"" + +static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_backend *backend, DBusMessage *m,
[pulseaudio-discuss] [PATCH 3/5] bluez5-util: always DISCONNECT on free
We always disconnect before free, move the state changed function in the free method. --- src/modules/bluetooth/bluez5-util.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c index b5c0726..777aca9 100644 --- a/src/modules/bluetooth/bluez5-util.c +++ b/src/modules/bluetooth/bluez5-util.c @@ -197,6 +197,8 @@ void pa_bluetooth_transport_put(pa_bluetooth_transport *t) { void pa_bluetooth_transport_free(pa_bluetooth_transport *t) { pa_assert(t); +transport_state_changed(t, PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED); + if (t->dispose) t->dispose (t); pa_hashmap_remove(t->device->discovery->transports, t->path); @@ -414,7 +416,6 @@ static void device_free(pa_bluetooth_device *d) { if (!(t = d->transports[i])) continue; -transport_state_changed(t, PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED); pa_bluetooth_transport_free(t); } @@ -1435,7 +1436,6 @@ static DBusMessage *endpoint_clear_configuration(DBusConnection *conn, DBusMessa if ((t = pa_hashmap_get(y->transports, path))) { pa_log_debug("Clearing transport %s profile %s", t->path, pa_bluetooth_profile_to_string(t->profile)); -transport_state_changed(t, PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED); pa_bluetooth_transport_free(t); } -- 1.9.3 ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
Re: [pulseaudio-discuss] [PATCH 0/5] Add simple HSP support in new native backend
> > This set of patches adds a simple handler for the HSP profile to bluez >> and makes a new card for each connected device. This makes it possible to >> send audio to a Headset. The profile is implemented in a new native >> headset backend. >> >> The first 4 patches are mostly some very small cleanups and little bits to >> implement the last patch. >> >> The patch also switches the default backend from ofono to native. >> > > Thanks for this patch set! > > Could you give a little explanation how this patch set relates to other > patch sets (like the currently pending ofono patch set and your previous > "null backend" patch set)? > These patches replace any of my previous patches, we now leave the null backend as it is and add a new -native backend. The idea for the native HSP backend is to have a simple implementation without any extra dependencies. It's hard to make ofono work on desktop machines and you don't need a full-blown phone stack to play sound through the headset in many cases. > > I e, to test this, I obviously need bluez 5, but does it also require any > pending patch set that it builds upon? And what can be expected, It does not need anything else than bluez5. It basically restores the functionality that got removed from bluez5 regarding the HSP profile. > working HSP playback but not recording? Any other missing functionality > and that you plan to add in later patches? You can expect a new device to show up when you pair you headset. You can then select the Headset Head Unit profile to make it connect using the HSP Profile. Both Playback and recording work. There is the future possibility to handle a button press and volume/gain of the device. I'm not sure we want to handle any of them. You could map the button press to cork/uncork the stream. You could possibly use the volume controls in addition to the software volume but there are only 15 volume levels. HSP only operates on 8Khz mono 16bits samples so perhaps it is not worth it. Wim ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
Re: [pulseaudio-discuss] [PATCH 5/5] backend-native: add a new native headset backend
> > +if (imtu) > > +*imtu = 48; > > + > > +if (omtu) > > +*omtu = 48; > > Out of curiosity, are these values fixed in the spec? > They are not but the kernel does not want to give us the real MTU values so this is hardcoded for now. > +if (events & PA_IO_EVENT_INPUT) { > > +char buf[512]; > > +ssize_t len; > > + > > +len = read (fd, buf, 511); > > +buf[len] = 0; > > +pa_log("RFCOMM << %s", buf); > > + > > +pa_log("RFCOMM >> OK"); > > +len = write (fd, "\r\nOK\r\n", 5); > > +if (len < 0) > > +pa_log_error("RFCOMM write error: %s", pa_cstrerror(errno)); > > Should we go to fail if this happens? > I'm ignoring this error for now, it's not critical and I expect real failures such as disconnection etc to be handled with the HANGUP and ERROR events. > d = pa_bluetooth_discovery_get_device_by_path(b->discovery, path); > > +if (d == NULL) { > > +pa_log_error("Device doesnt exist for %s", path); > > +goto fail; > > +} > > + > > +if (pa_streq(handler, HSP_AG_PROFILE)) { > > +p = PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT; > > +} else > > +goto refused; > > Is the else possible at all? > > It should not be, I'll make it assert. > > +dbus_message_iter_recurse(&element_i, &dict_i); > > + > > +if (key == NULL) > > +break; > > +if (dbus_message_iter_get_arg_type(&dict_i) != DBUS_TYPE_STRING) > > +break; > > + > > +dbus_message_iter_get_basic(&dict_i, &key); > > + > > +if (!dbus_message_iter_next(&dict_i)) > > +break; > > + > > +if (dbus_message_iter_get_arg_type(&dict_i) != > DBUS_TYPE_VARIANT) > > +break; > > + > > +pa_log_debug("key=%s", key); > > + > > +dbus_message_iter_next(&element_i); > > +} > > If we're not doing anything with the keys, maybe we don't need to > parse any of this? > I'll remove it. It should only contain a version number but I don't plan to do anything with that for now. > > > +trfc = pa_xnew0(struct transport_rfcomm, 1); > > +trfc->rfcomm_fd = fd; > > +trfc->rfcomm_io = b->core->mainloop->io_new(b->core->mainloop, fd, > PA_IO_EVENT_INPUT|PA_IO_EVENT_HANGUP, > > +rfcomm_io_callback, t); > > Can reading/writing on the RFCOMM fd block the mainloop for long periods? > The socket flags are set G_IO_FLAG_NONBLOCK in bluez, AFAICS. > > > +trfc->backend = b; > > Can the backend be destroyed before the transport? Maybe we should > only reference the mainloop instead of the backend here. > I'll have a look if all cleanup scenarios work fine. We should probably just ref the mainloop, indeed. > > +static DBusMessage *profile_request_disconnection(DBusConnection *conn, > DBusMessage *m, void *userdata) { > > +DBusMessage *r; > > + > > +pa_assert_se(r = dbus_message_new_method_return(m)); > > How is the transport torn down in this case? > Bluez closes the RFCOMM socket, that makes us stop. Another case is when the SCO socket is closed. We free the transport, which makes us remove the device. I'll see if I can close it myself instead. > > > + > > +} else if (dbus_message_is_method_call(m, BLUEZ_PROFILE_INTERFACE, > "Release")) { > > +} else if (dbus_message_is_method_call(m, BLUEZ_PROFILE_INTERFACE, > "Cancel")) { > > There doesn't actually seem to be a 'Cancel' method. > I will remove it. It looks like it is supposed to be part of the API here: http://git.kernel.org/cgit/bluetooth/bluez.git/tree/profiles/iap/main.c#n228 and in some exampled, but it does not look like it is used here: is http://git.kernel.org/cgit/bluetooth/bluez.git/tree/src/profile.c > +switch(profile) { > > +case PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT: > > +object_name = HSP_AG_PROFILE; > > +uuid = PA_BLUETOOTH_UUID_HSP_AG; > > +break; > > +default: > > +pa_assert_not_reached(); > > +break; > > +} > > + > pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(b->connection), > > + object_name, &vtable_profile, b)); > > +register_profile (b, object_name, uuid); > > There doesn't seem to be a way to handle failure when registering the > profile. Should we perhaps be unregistering the object path? > > yes, will do. > > + > > +profile_done(backend, PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT); > > backend->connection needs to be unref'ed here. > > Will do, Thanks for reviewing, will post new patches with all suggestions and fixes soon, Wim ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
Re: [pulseaudio-discuss] [PATCH 0/5] Add simple HSP support in new native backend
> > > With BlueZ 4 we support hardware volume, i.e. the 15 volume levels. This > is actually quite important, because if you rely on software volume, > there will be two independent volume controls for the headset: the > software volume in PulseAudio, and the headset's internal volume. Not > user-friendly at all. > I had a look at the bluez4 code for that, it should not be hard to add volume handling as well. Wim ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
[pulseaudio-discuss] [PATCH 3/3] backend-native: add a new native headset backend
Add a simple native headset backend that implements support for the blutooth HSP profile. This allows pulseaudio to output audio to a Headset using the HSP profile. Make the native backend the default. --- configure.ac | 6 +- src/modules/bluetooth/backend-native.c | 443 + 2 files changed, 446 insertions(+), 3 deletions(-) create mode 100644 src/modules/bluetooth/backend-native.c diff --git a/configure.ac b/configure.ac index f13ddb0..5b3f3cc 100644 --- a/configure.ac +++ b/configure.ac @@ -1030,14 +1030,14 @@ AM_CONDITIONAL([HAVE_BLUEZ], [test "x$HAVE_BLUEZ" = x1]) ## Bluetooth Headset profiles backend ## AC_ARG_WITH(bluetooth_headset_backend, -AS_HELP_STRING([--with-bluetooth-headset-backend=],[Backend for Bluetooth headset profiles (ofono)])) + AS_HELP_STRING([--with-bluetooth-headset-backend=],[Backend for Bluetooth headset profiles (native)])) if test -z "$with_bluetooth_headset_backend" ; then -BLUETOOTH_HEADSET_BACKEND=ofono +BLUETOOTH_HEADSET_BACKEND=native else BLUETOOTH_HEADSET_BACKEND=$with_bluetooth_headset_backend fi -AS_IF([test "x$BLUETOOTH_HEADSET_BACKEND" != "xofono" && test "x$BLUETOOTH_HEADSET_BACKEND" != "xnull"], +AS_IF([test "x$BLUETOOTH_HEADSET_BACKEND" != "xofono" && test "x$BLUETOOTH_HEADSET_BACKEND" != "xnull" && test "x$BLUETOOTH_HEADSET_BACKEND" != "xnative"], [AC_MSG_ERROR([*** Invalid Bluetooth Headset backend])]) AC_SUBST(BLUETOOTH_HEADSET_BACKEND) diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c new file mode 100644 index 000..b3c4220 --- /dev/null +++ b/src/modules/bluetooth/backend-native.c @@ -0,0 +1,443 @@ +/*** + This file is part of PulseAudio. + + Copyright 2014 Wim Taymans + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "bluez5-util.h" + +struct pa_bluetooth_backend { + pa_core *core; + pa_dbus_connection *connection; + pa_bluetooth_discovery *discovery; + + PA_LLIST_HEAD(pa_dbus_pending, pending); +}; + +struct transport_rfcomm { +int rfcomm_fd; +pa_io_event *rfcomm_io; +pa_mainloop_api *mainloop; +}; + +#define BLUEZ_SERVICE "org.bluez" +#define BLUEZ_MEDIA_TRANSPORT_INTERFACE BLUEZ_SERVICE ".MediaTransport1" + +#define BLUEZ_ERROR_NOT_SUPPORTED "org.bluez.Error.NotSupported" + +#define BLUEZ_PROFILE_MANAGER_INTERFACE BLUEZ_SERVICE ".ProfileManager1" +#define BLUEZ_PROFILE_INTERFACE BLUEZ_SERVICE ".Profile1" + +#define HSP_AG_PROFILE "/Profile/HSPAGProfile" + +#define PROFILE_INTROSPECT_XML \ +DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ +""\ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" "\ +" " \ +" " \ +" " \ +"" + +static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_backend *backend, DBusMessage *m, +DBusPendingCallNotifyFunction func, void *call_data) { +pa_dbus_pending *p; +DBusPendingCall *ca
[pulseaudio-discuss] [PATCH 0/3] Add simple HSP support in new native backend
This set of patches adds a simple handler for the HSP profile to bluez and makes a new card for each connected device. This makes it possible to send audio to a Headset. The profile is implemented in a new native headset backend. The first 2 patches are mostly some very small cleanups and little bits to implement the last patch. The patch also switches the default backend from ofono to native. This patch replaces the previously posted native backend patches and incorporates the suggestions I received. Wim Taymans (3): bluez5-device: use get_profile_direction bluez5-util: add dispose function backend-native: add a new native headset backend configure.ac | 6 +- src/modules/bluetooth/backend-native.c | 443 +++ src/modules/bluetooth/bluez5-util.c | 2 + src/modules/bluetooth/bluez5-util.h | 2 + src/modules/bluetooth/module-bluez5-device.c | 32 +- 5 files changed, 465 insertions(+), 20 deletions(-) create mode 100644 src/modules/bluetooth/backend-native.c -- 1.9.3 ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
[pulseaudio-discuss] [PATCH 1/3] bluez5-device: use get_profile_direction
Use the get_profile_direction() helper function to decide when to add a source and a sink instead of enumerating profiles. --- src/modules/bluetooth/module-bluez5-device.c | 32 +--- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c index fdf4078..49f4676 100644 --- a/src/modules/bluetooth/module-bluez5-device.c +++ b/src/modules/bluetooth/module-bluez5-device.c @@ -1210,6 +1210,19 @@ static int setup_transport(struct userdata *u) { } /* Run from main thread */ +static pa_direction_t get_profile_direction(pa_bluetooth_profile_t p) { +static const pa_direction_t profile_direction[] = { +[PA_BLUETOOTH_PROFILE_A2DP_SINK] = PA_DIRECTION_OUTPUT, +[PA_BLUETOOTH_PROFILE_A2DP_SOURCE] = PA_DIRECTION_INPUT, +[PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT, +[PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT, +[PA_BLUETOOTH_PROFILE_OFF] = 0 +}; + +return profile_direction[p]; +} + +/* Run from main thread */ static int init_profile(struct userdata *u) { int r = 0; pa_assert(u); @@ -1220,13 +1233,11 @@ static int init_profile(struct userdata *u) { pa_assert(u->transport); -if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT || -u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) +if (get_profile_direction (u->profile) & PA_DIRECTION_OUTPUT) if (add_sink(u) < 0) r = -1; -if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT || -u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) +if (get_profile_direction (u->profile) & PA_DIRECTION_INPUT) if (add_source(u) < 0) r = -1; @@ -1546,19 +1557,6 @@ static char *cleanup_name(const char *name) { } /* Run from main thread */ -static pa_direction_t get_profile_direction(pa_bluetooth_profile_t p) { -static const pa_direction_t profile_direction[] = { -[PA_BLUETOOTH_PROFILE_A2DP_SINK] = PA_DIRECTION_OUTPUT, -[PA_BLUETOOTH_PROFILE_A2DP_SOURCE] = PA_DIRECTION_INPUT, -[PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT, -[PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT, -[PA_BLUETOOTH_PROFILE_OFF] = 0 -}; - -return profile_direction[p]; -} - -/* Run from main thread */ static pa_available_t get_port_availability(struct userdata *u, pa_direction_t direction) { pa_available_t result = PA_AVAILABLE_NO; unsigned i; -- 1.9.3 ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
[pulseaudio-discuss] [PATCH 2/3] bluez5-util: add dispose function
Add a dispose function to the transport that is called before freeing the transport. Useful for cleaning up extra userdata. --- src/modules/bluetooth/bluez5-util.c | 2 ++ src/modules/bluetooth/bluez5-util.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c index 8bb57f4..d847255 100644 --- a/src/modules/bluetooth/bluez5-util.c +++ b/src/modules/bluetooth/bluez5-util.c @@ -203,6 +203,8 @@ void pa_bluetooth_transport_unlink(pa_bluetooth_transport *t) { void pa_bluetooth_transport_free(pa_bluetooth_transport *t) { pa_assert(t); +if (t->dispose) +t->dispose (t); pa_hashmap_remove(t->device->discovery->transports, t->path); pa_xfree(t->owner); pa_xfree(t->path); diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h index 8db4a17..fde2e78 100644 --- a/src/modules/bluetooth/bluez5-util.h +++ b/src/modules/bluetooth/bluez5-util.h @@ -60,6 +60,7 @@ typedef enum pa_bluetooth_transport_state { typedef int (*pa_bluetooth_transport_acquire_cb)(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu); typedef void (*pa_bluetooth_transport_release_cb)(pa_bluetooth_transport *t); +typedef void (*pa_bluetooth_transport_dispose_cb)(pa_bluetooth_transport *t); struct pa_bluetooth_transport { pa_bluetooth_device *device; @@ -76,6 +77,7 @@ struct pa_bluetooth_transport { pa_bluetooth_transport_acquire_cb acquire; pa_bluetooth_transport_release_cb release; +pa_bluetooth_transport_dispose_cb dispose; void *userdata; }; -- 1.9.3 ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
[pulseaudio-discuss] [PATCH 1/4] bluez5-device: use get_profile_direction
Use the get_profile_direction() helper function to decide when to add a source and a sink instead of enumerating profiles. --- src/modules/bluetooth/module-bluez5-device.c | 32 +--- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c index 48e498b..15731d1 100644 --- a/src/modules/bluetooth/module-bluez5-device.c +++ b/src/modules/bluetooth/module-bluez5-device.c @@ -1210,6 +1210,19 @@ static int setup_transport(struct userdata *u) { } /* Run from main thread */ +static pa_direction_t get_profile_direction(pa_bluetooth_profile_t p) { +static const pa_direction_t profile_direction[] = { +[PA_BLUETOOTH_PROFILE_A2DP_SINK] = PA_DIRECTION_OUTPUT, +[PA_BLUETOOTH_PROFILE_A2DP_SOURCE] = PA_DIRECTION_INPUT, +[PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT, +[PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT, +[PA_BLUETOOTH_PROFILE_OFF] = 0 +}; + +return profile_direction[p]; +} + +/* Run from main thread */ static int init_profile(struct userdata *u) { int r = 0; pa_assert(u); @@ -1220,13 +1233,11 @@ static int init_profile(struct userdata *u) { pa_assert(u->transport); -if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT || -u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) +if (get_profile_direction (u->profile) & PA_DIRECTION_OUTPUT) if (add_sink(u) < 0) r = -1; -if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT || -u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) +if (get_profile_direction (u->profile) & PA_DIRECTION_INPUT) if (add_source(u) < 0) r = -1; @@ -1546,19 +1557,6 @@ static char *cleanup_name(const char *name) { } /* Run from main thread */ -static pa_direction_t get_profile_direction(pa_bluetooth_profile_t p) { -static const pa_direction_t profile_direction[] = { -[PA_BLUETOOTH_PROFILE_A2DP_SINK] = PA_DIRECTION_OUTPUT, -[PA_BLUETOOTH_PROFILE_A2DP_SOURCE] = PA_DIRECTION_INPUT, -[PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT, -[PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT, -[PA_BLUETOOTH_PROFILE_OFF] = 0 -}; - -return profile_direction[p]; -} - -/* Run from main thread */ static pa_available_t get_port_availability(struct userdata *u, pa_direction_t direction) { pa_available_t result = PA_AVAILABLE_NO; unsigned i; -- 1.9.3 ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
[pulseaudio-discuss] [PATCH 3/4] backend-native: add a new native headset backend
Add a simple native headset backend that implements support for the blutooth HSP profile. This allows pulseaudio to output audio to a Headset using the HSP profile. Make the native backend the default. --- configure.ac | 6 +- src/modules/bluetooth/backend-native.c | 443 + 2 files changed, 446 insertions(+), 3 deletions(-) create mode 100644 src/modules/bluetooth/backend-native.c diff --git a/configure.ac b/configure.ac index 74bea71..9a595cb 100644 --- a/configure.ac +++ b/configure.ac @@ -1032,14 +1032,14 @@ AM_CONDITIONAL([HAVE_BLUEZ], [test "x$HAVE_BLUEZ" = x1]) ## Bluetooth Headset profiles backend ## AC_ARG_WITH(bluetooth_headset_backend, -AS_HELP_STRING([--with-bluetooth-headset-backend=],[Backend for Bluetooth headset profiles (ofono)])) + AS_HELP_STRING([--with-bluetooth-headset-backend=],[Backend for Bluetooth headset profiles (native)])) if test -z "$with_bluetooth_headset_backend" ; then -BLUETOOTH_HEADSET_BACKEND=ofono +BLUETOOTH_HEADSET_BACKEND=native else BLUETOOTH_HEADSET_BACKEND=$with_bluetooth_headset_backend fi -AS_IF([test "x$BLUETOOTH_HEADSET_BACKEND" != "xofono" && test "x$BLUETOOTH_HEADSET_BACKEND" != "xnull"], +AS_IF([test "x$BLUETOOTH_HEADSET_BACKEND" != "xofono" && test "x$BLUETOOTH_HEADSET_BACKEND" != "xnull" && test "x$BLUETOOTH_HEADSET_BACKEND" != "xnative"], [AC_MSG_ERROR([*** Invalid Bluetooth Headset backend])]) AC_SUBST(BLUETOOTH_HEADSET_BACKEND) diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c new file mode 100644 index 000..b3c4220 --- /dev/null +++ b/src/modules/bluetooth/backend-native.c @@ -0,0 +1,443 @@ +/*** + This file is part of PulseAudio. + + Copyright 2014 Wim Taymans + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "bluez5-util.h" + +struct pa_bluetooth_backend { + pa_core *core; + pa_dbus_connection *connection; + pa_bluetooth_discovery *discovery; + + PA_LLIST_HEAD(pa_dbus_pending, pending); +}; + +struct transport_rfcomm { +int rfcomm_fd; +pa_io_event *rfcomm_io; +pa_mainloop_api *mainloop; +}; + +#define BLUEZ_SERVICE "org.bluez" +#define BLUEZ_MEDIA_TRANSPORT_INTERFACE BLUEZ_SERVICE ".MediaTransport1" + +#define BLUEZ_ERROR_NOT_SUPPORTED "org.bluez.Error.NotSupported" + +#define BLUEZ_PROFILE_MANAGER_INTERFACE BLUEZ_SERVICE ".ProfileManager1" +#define BLUEZ_PROFILE_INTERFACE BLUEZ_SERVICE ".Profile1" + +#define HSP_AG_PROFILE "/Profile/HSPAGProfile" + +#define PROFILE_INTROSPECT_XML \ +DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ +""\ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" "\ +" " \ +" " \ +" " \ +"" + +static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_backend *backend, DBusMessage *m, +DBusPendingCallNotifyFunction func, void *call_data) { +pa_dbus_pending *p; +DBusPendingCall *ca
[pulseaudio-discuss] [PATCH 2/4] bluez5-util: add dispose function
Add a dispose function to the transport that is called before freeing the transport. Useful for cleaning up extra userdata. --- src/modules/bluetooth/bluez5-util.c | 2 ++ src/modules/bluetooth/bluez5-util.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c index 1ee2f33..50f977e 100644 --- a/src/modules/bluetooth/bluez5-util.c +++ b/src/modules/bluetooth/bluez5-util.c @@ -205,6 +205,8 @@ void pa_bluetooth_transport_unlink(pa_bluetooth_transport *t) { void pa_bluetooth_transport_free(pa_bluetooth_transport *t) { pa_assert(t); +if (t->dispose) +t->dispose (t); pa_bluetooth_transport_unlink(t); pa_xfree(t->owner); diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h index 8db4a17..fde2e78 100644 --- a/src/modules/bluetooth/bluez5-util.h +++ b/src/modules/bluetooth/bluez5-util.h @@ -60,6 +60,7 @@ typedef enum pa_bluetooth_transport_state { typedef int (*pa_bluetooth_transport_acquire_cb)(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu); typedef void (*pa_bluetooth_transport_release_cb)(pa_bluetooth_transport *t); +typedef void (*pa_bluetooth_transport_dispose_cb)(pa_bluetooth_transport *t); struct pa_bluetooth_transport { pa_bluetooth_device *device; @@ -76,6 +77,7 @@ struct pa_bluetooth_transport { pa_bluetooth_transport_acquire_cb acquire; pa_bluetooth_transport_release_cb release; +pa_bluetooth_transport_dispose_cb dispose; void *userdata; }; -- 1.9.3 ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
[pulseaudio-discuss] [PATCH 4/4] backend-native: implement volume control
Parse the gain changed AT commands from the headset and fire 2 new hooks as a result. The device will connect to those hooks and change the source/sink volumes. When the source/sink volume changes, set the gain on the microphone or speaker respectively. Make sure we do nothing if the transport can not handle the gain changes. --- src/modules/bluetooth/backend-native.c | 47 + src/modules/bluetooth/bluez5-util.h | 9 ++ src/modules/bluetooth/module-bluez5-device.c | 140 +++ 3 files changed, 196 insertions(+) diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c index b3c4220..2302754 100644 --- a/src/modules/bluetooth/backend-native.c +++ b/src/modules/bluetooth/backend-native.c @@ -113,6 +113,7 @@ static int bluez5_sco_acquire_cb(pa_bluetooth_transport *t, bool optional, size_ src_addr = d->adapter->address; dst_addr = d->address; +/* don't use ba2str to avoid -lbluetooth */ for (i = 5; i >= 0; i--, src_addr += 3) src.b[i] = strtol(src_addr, NULL, 16); for (i = 5; i >= 0; i--, dst_addr += 3) @@ -226,11 +227,21 @@ static void rfcomm_io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_i if (events & PA_IO_EVENT_INPUT) { char buf[512]; ssize_t len; +int gain; len = read (fd, buf, 511); buf[len] = 0; pa_log("RFCOMM << %s", buf); +if (sscanf (buf, "AT+VGS=%d", &gain) == 1) { + t->speaker_gain = gain; + pa_hook_fire(pa_bluetooth_discovery_hook (t->device->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED), t); + +} else if (sscanf (buf, "AT+VGM=%d", &gain) == 1) { + t->microphone_gain = gain; + pa_hook_fire(pa_bluetooth_discovery_hook (t->device->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED), t); +} + pa_log("RFCOMM >> OK"); len = write (fd, "\r\nOK\r\n", 5); /* we ignore any errors, it's not critical and real errors should @@ -256,6 +267,40 @@ static void transport_dispose(pa_bluetooth_transport *t) { pa_xfree(trfc); } +static void set_speaker_gain(pa_bluetooth_transport *t, uint16_t gain) { +struct transport_rfcomm *trfc = t->userdata; +char buf[512]; +ssize_t len, written; + +if (t->speaker_gain == gain) + return; + +t->speaker_gain = gain; + +len = sprintf (buf, "+VGS=%d\r\n", gain); +pa_log("RFCOMM >> +VGS=%d", gain); +written = write (trfc->rfcomm_fd, buf, len); +if (written != len) +pa_log_error("RFCOMM write error: %s", pa_cstrerror(errno)); +} + +static void set_microphone_gain(pa_bluetooth_transport *t, uint16_t gain) { +struct transport_rfcomm *trfc = t->userdata; +char buf[512]; +ssize_t len, written; + +if (t->microphone_gain == gain) + return; + +t->microphone_gain = gain; + +len = sprintf (buf, "+VGM=%d\r\n", gain); +pa_log("RFCOMM >> +VGM=%d", gain); +written = write (trfc->rfcomm_fd, buf, len); +if (written != len) +pa_log_error("RFCOMM write error: %s", pa_cstrerror(errno)); + +} static DBusMessage *profile_new_connection(DBusConnection *conn, DBusMessage *m, void *userdata) { pa_bluetooth_backend *b = userdata; @@ -303,6 +348,8 @@ static DBusMessage *profile_new_connection(DBusConnection *conn, DBusMessage *m, t->acquire = bluez5_sco_acquire_cb; t->release = bluez5_sco_release_cb; t->dispose = transport_dispose; +t->set_speaker_gain = set_speaker_gain; +t->set_microphone_gain = set_microphone_gain; trfc = pa_xnew0(struct transport_rfcomm, 1); trfc->rfcomm_fd = fd; diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h index fde2e78..1258f07 100644 --- a/src/modules/bluetooth/bluez5-util.h +++ b/src/modules/bluetooth/bluez5-util.h @@ -40,6 +40,8 @@ typedef struct pa_bluetooth_backend pa_bluetooth_backend; typedef enum pa_bluetooth_hook { PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED, /* Call data: pa_bluetooth_device */ PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED,/* Call data: pa_bluetooth_transport */ +PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED, /* Call data: pa_bluetooth_transport */ +PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED, /* Call data: pa_bluetooth_transport */ PA_BLUETOOTH_HOOK_MAX } pa_bluetooth_hook_t; @@ -61,6 +63,8 @@ typedef enum pa_bluetooth_transport_state { typedef int (*pa_bluetooth_transport_acquire_cb)(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu); typedef void (*pa_bluetooth_transport_release_cb)(pa_bluetooth_transport *t); typedef void (*pa_bluetooth_transport_dispose_cb)(pa_bluetooth_transport *t); +typedef void (*pa_bluetooth_transport_set_speaker_gain_cb)(pa_bluetooth_transport *t, uint16_t gain); +typedef void (*pa_bluetooth_transport_set_microphone_gain_cb)(pa_bluetooth_transpo
[pulseaudio-discuss] [PATCH 0/4] Add simple HSP support in new native backend v3
This set of patches adds a simple handler for the HSP profile to bluez and makes a new card for each connected device. This makes it possible to send audio to a Headset. The profile is implemented in a new native headset backend. The first 2 patches are mostly some very small cleanups and little bits to implement the following patches. The patches also switches the default backend from ofono to native. This patchset replaces the previously posted native backend patches and incorporates the suggestions I received to date. Wim Taymans (4): bluez5-device: use get_profile_direction bluez5-util: add dispose function backend-native: add a new native headset backend backend-native: implement volume control configure.ac | 6 +- src/modules/bluetooth/backend-native.c | 490 +++ src/modules/bluetooth/bluez5-util.c | 2 + src/modules/bluetooth/bluez5-util.h | 11 + src/modules/bluetooth/module-bluez5-device.c | 172 +- 5 files changed, 661 insertions(+), 20 deletions(-) create mode 100644 src/modules/bluetooth/backend-native.c -- 1.9.3 ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
Re: [pulseaudio-discuss] Master is frozen
Can you check in the log if the volume AT commands are sent to the headset? What I can think of is maybe that the command is not formatted properly (like missing a \n\r somewhere, I've seen some headset be picky about that). Wim On 14 November 2014 10:39, Arun Raghavan wrote: > > On 14 Nov 2014 15:00, "David Henningsson" > wrote: > > > > > > > > On 2014-11-14 07:07, Arun Raghavan wrote: > >> > >> Hello, > >> As promised, master is now frozen. Please do not push anything other > >> than essential fixes.I'd like to have some stability testing of HSP > >> before rolling 6.0 RC1. > > > > > > I'd like to have my HSP compilation patches in before 6.0 RC1. Can I > push them? > > I thought this was done. Please go ahead. > > >> Any help here is appreciated. > > > > > > Ok, with some trouble [1] I installed bluez5 in Ubuntu 15.04 and ran > PulseAudio git master on top of that, with the native headset backend. > > > > Result: Headset card shows up. Switching between A2DP and HSP seems to > be working. The hsp profile is called "headset_head_unit", which is a > change from bluez4, but it's nothing that seems to cause problems for me. > > > > HSP playback and recording seems to work. Volume control (playback) and > gain control (recording) does not work, i e, changing volume and/or gain > has no effect on actual volume/gain. > > Thanks for trying this. At least the volume bit is the same for us both. > Will need to investigate that. Wim/Luiz, any ideas? > > If it was otherwise stable for you, that if good news indeed. > > Cheers, > Arun > ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
Re: [pulseaudio-discuss] Master is frozen
Can you compare with a hcidump of working volume? On 14 November 2014 10:46, Arun Raghavan wrote: > > On 14 Nov 2014 15:13, "Wim Taymans" wrote: > > > > Can you check in the log if the volume AT commands are sent to the > headset? What I can think of is maybe that the command is not formatted > properly (like missing a \n\r somewhere, I've seen some headset be picky > about that). > > I'd checked that the AT commands are going out fine in hcidump. Tested > with 2 headsets here, both did not work. > > I did add a couple of fixes on top of your volume patches, btw. > > --Arun > ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
Re: [pulseaudio-discuss] Master is frozen
After reading the HSP spec some more, the volume controls are optional. It should be mentioned in the SDP if the remote volume is supported (with the BT assigned number 0x302 apparently). Not sure how to get to that info.. Wim On 14 November 2014 11:04, Arun Raghavan wrote: > > On 14 Nov 2014 15:31, "Wim Taymans" wrote: > > > > Can you compare with a hcidump of working volume? > > It never actually worked for me. If you have one that works, I can compare > with what I get here ( well probably take a day or two). > > -- Arun > ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
[pulseaudio-discuss] [PATCH] bluetooth: set gain correctly
Send the right command to set the speaker and microphone gain. --- src/modules/bluetooth/backend-native.c | 8 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c index 86af422..cea2db2 100644 --- a/src/modules/bluetooth/backend-native.c +++ b/src/modules/bluetooth/backend-native.c @@ -285,8 +285,8 @@ static void set_speaker_gain(pa_bluetooth_transport *t, uint16_t gain) { t->speaker_gain = gain; -len = sprintf(buf, "AT+VGS=%d\r", gain); -pa_log_debug("RFCOMM >> AT+VGS=%d", gain); +len = sprintf(buf, "+VGS=%d\r", gain); +pa_log_debug("RFCOMM >> +VGS=%d", gain); written = write(trfc->rfcomm_fd, buf, len); @@ -304,8 +304,8 @@ static void set_microphone_gain(pa_bluetooth_transport *t, uint16_t gain) { t->microphone_gain = gain; -len = sprintf(buf, "AT+VGM=%d\r", gain); -pa_log_debug("RFCOMM >> AT+VGM=%d", gain); +len = sprintf(buf, "+VGM=%d\r", gain); +pa_log_debug("RFCOMM >> +VGM=%d", gain); written = write (trfc->rfcomm_fd, buf, len); -- 1.9.3 ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
[pulseaudio-discuss] [PATCH] bluetooth: set gain correctly
Send the right command to set the speaker and microphone gain. Note that setting the volume on the Headset should use the unsolicited result code. Receiving the volume from the Headset uses the AT command. --- src/modules/bluetooth/backend-native.c | 8 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c index 86af422..8407672 100644 --- a/src/modules/bluetooth/backend-native.c +++ b/src/modules/bluetooth/backend-native.c @@ -285,8 +285,8 @@ static void set_speaker_gain(pa_bluetooth_transport *t, uint16_t gain) { t->speaker_gain = gain; -len = sprintf(buf, "AT+VGS=%d\r", gain); -pa_log_debug("RFCOMM >> AT+VGS=%d", gain); +len = sprintf(buf, "\r\n+VGS=%d\r\n", gain); +pa_log_debug("RFCOMM >> +VGS=%d", gain); written = write(trfc->rfcomm_fd, buf, len); @@ -304,8 +304,8 @@ static void set_microphone_gain(pa_bluetooth_transport *t, uint16_t gain) { t->microphone_gain = gain; -len = sprintf(buf, "AT+VGM=%d\r", gain); -pa_log_debug("RFCOMM >> AT+VGM=%d", gain); +len = sprintf(buf, "\r\n+VGM=%d\r\n", gain); +pa_log_debug("RFCOMM >> +VGM=%d", gain); written = write (trfc->rfcomm_fd, buf, len); -- 1.9.3 ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
[pulseaudio-discuss] [PATCH] Fix the gain settings on Headset
This patch fixes the gain settings for Headsets by sending the right commands. It replaces the previous patch. Wim Taymans (1): bluetooth: set gain correctly src/modules/bluetooth/backend-native.c | 8 1 file changed, 4 insertions(+), 4 deletions(-) -- 1.9.3 ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
[pulseaudio-discuss] [PATCH] svolume.orc: avoid parameter loading undefined behaviour
In some cases, depending on the instruction that performs the load, orc ignores the size of the parameter when loading it for the first time. Explicitly load the parameter into a temp to make sure it is loaded correctly, like we do for the 2ch case. See https://bugzilla.gnome.org/show_bug.cgi?id=742271 --- src/pulsecore/svolume.orc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pulsecore/svolume.orc b/src/pulsecore/svolume.orc index 0edbefb..f869893 100644 --- a/src/pulsecore/svolume.orc +++ b/src/pulsecore/svolume.orc @@ -45,13 +45,15 @@ .function pa_volume_s16ne_orc_1ch .dest 2 samples int16_t -.param 4 v int32_t +.param 4 vols int32_t +.temp 4 v .temp 2 vh .temp 4 s .temp 4 mh .temp 4 ml .temp 4 signc +loadpl v, vols convuwl s, samples x2 cmpgtsw signc, 0, s x2 andw signc, signc, v -- 1.9.3 ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
[pulseaudio-discuss] [PATCH] backend-native: add support for the HSP HeadeSet profile
In addition to the HSP Audio Gateway, also add support for the HeadSet profile in the native bluetooth backend. With this profile you can use pulseaudio as a headset. In the headset role, we create source and sink to receive and send the samples from the gateway, respectively. The loopback device will automatically link these to a sink and source for playback. Because this makes the source the speaker and the sink the microphone, we need to reverse the roles of source and sink compared to the gateway role. In the gateway role, adjusting the sink volume generates a +VGS command to set the volume on the headset. Likewise, receiving AT+VGS updates the sink volume. In the headset role, receiving a +VGS should set the source volume and any source volume changes should be reported back to the gateway with AT+VGS. --- src/modules/bluetooth/backend-native.c | 98 +--- src/modules/bluetooth/module-bluez5-device.c | 32 +++-- 2 files changed, 101 insertions(+), 29 deletions(-) diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c index 8d9d95c..952cd68 100644 --- a/src/modules/bluetooth/backend-native.c +++ b/src/modules/bluetooth/backend-native.c @@ -59,6 +59,9 @@ struct transport_rfcomm { #define BLUEZ_PROFILE_INTERFACE BLUEZ_SERVICE ".Profile1" #define HSP_AG_PROFILE "/Profile/HSPAGProfile" +#define HSP_HS_PROFILE "/Profile/HSPHSProfile" + +#define HSP_HS_DEFAULT_CHANNEL 3 #define PROFILE_INTROSPECT_XML \ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ @@ -140,7 +143,10 @@ static int bluez5_sco_acquire_cb(pa_bluetooth_transport *t, bool optional, size_ pa_log_info ("doing connect\n"); err = connect(sock, (struct sockaddr *) &addr, sizeof(addr)); if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS)) { -pa_log_error("connect(): %s", pa_cstrerror(errno)); +if (optional) +pa_log_info("optional connect(): %s", pa_cstrerror(errno)); +else +pa_log_error("connect(): %s", pa_cstrerror(errno)); goto fail_close; } @@ -200,8 +206,10 @@ finish: static void register_profile(pa_bluetooth_backend *b, const char *profile, const char *uuid) { DBusMessage *m; DBusMessageIter i, d; +dbus_bool_t autoconnect; +dbus_uint16_t version, chan; -pa_log_debug("Registering Profile %s", profile); +pa_log_debug("Registering Profile %s %s", profile, uuid); pa_assert_se(m = dbus_message_new_method_call(BLUEZ_SERVICE, "/org/bluez", BLUEZ_PROFILE_MANAGER_INTERFACE, "RegisterProfile")); @@ -210,6 +218,14 @@ static void register_profile(pa_bluetooth_backend *b, const char *profile, const dbus_message_iter_append_basic(&i, DBUS_TYPE_STRING, &uuid); dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &d); +if (pa_streq (uuid, PA_BLUETOOTH_UUID_HSP_HS)) { +autoconnect = 0; +pa_dbus_append_basic_variant_dict_entry(&d, "AutoConnect", DBUS_TYPE_BOOLEAN, &autoconnect); +chan = HSP_HS_DEFAULT_CHANNEL; +pa_dbus_append_basic_variant_dict_entry(&d, "Channel", DBUS_TYPE_UINT16, &chan); +version = 0x0102; +pa_dbus_append_basic_variant_dict_entry(&d, "Version", DBUS_TYPE_UINT16, &version); +} dbus_message_iter_close_container(&i, &d); send_and_add_to_pending(b, m, register_profile_reply, pa_xstrdup(profile)); @@ -229,29 +245,35 @@ static void rfcomm_io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_i if (events & PA_IO_EVENT_INPUT) { char buf[512]; ssize_t len; -int gain; +int gain, do_reply = FALSE; len = read(fd, buf, 511); buf[len] = 0; pa_log_debug("RFCOMM << %s", buf); -if (sscanf(buf, "AT+VGS=%d", &gain) == 1) { - t->speaker_gain = gain; - pa_hook_fire(pa_bluetooth_discovery_hook(t->device->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED), t); - -} else if (sscanf(buf, "AT+VGM=%d", &gain) == 1) { - t->microphone_gain = gain; - pa_hook_fire(pa_bluetooth_discovery_hook(t->device->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED), t); +if (sscanf(buf, "AT+VGS=%d", &gain) == 1 || sscanf(buf, "\r\n+VGM=%d\r\n", &gain) == 1) { +t->speaker_gain = gain; +pa_hook_fire(pa_bluetooth_discovery_hook(t->device->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED), t); +do_reply = TRUE; + +} else if (sscanf(buf, "AT+VGM=%d", &gain) == 1 || sscanf(buf, "\r\n+VGS=%d\r\n", &gain) == 1) { +t->microphone_gain = gain; +pa_hook_fire(pa_bluetooth_discovery_hook(t->device->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED), t);
Re: [pulseaudio-discuss] [PATCH] backend-native: add support for the HSP HeadeSet profile
It should work if you use pavucontrol to toggle the profile on the card (which makes the headset start an SCO connection). Not sure what's up with that, maybe you're right and it needs to listen for SCO connections started from the gateway, I'll have to try. Wim On 19 February 2015 at 18:12, Georg Chini wrote: > On 17.02.2015 15:34, Wim Taymans wrote: > >> In addition to the HSP Audio Gateway, also add support for the HeadSet >> profile in the native bluetooth backend. With this profile you can use >> pulseaudio as a headset. >> >> >> Just tested it with my old Nokia 6230. Connects / disconnects OK, but no > sound. > I do not see any code that listens for incoming SCO connections. Is the > support > still incomplete? > ___ > pulseaudio-discuss mailing list > pulseaudio-discuss@lists.freedesktop.org > http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss > ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
[pulseaudio-discuss] [PATCH] backend-native: add support for the HSP HeadeSet profile
In addition to the HSP Audio Gateway, also add support for the HeadSet profile in the native bluetooth backend. With this profile you can use pulseaudio as a headset. In the headset role, we create source and sink to receive and send the samples from the gateway, respectively. The loopback device will automatically link these to a sink and source for playback. Because this makes the source the speaker and the sink the microphone, we need to reverse the roles of source and sink compared to the gateway role. In the gateway role, adjusting the sink volume generates a +VGS command to set the volume on the headset. Likewise, receiving AT+VGS updates the sink volume. In the headset role, receiving a +VGS should set the source volume and any source volume changes should be reported back to the gateway with AT+VGS. --- src/modules/bluetooth/backend-native.c | 238 ++- src/modules/bluetooth/module-bluez5-device.c | 32 +++- 2 files changed, 222 insertions(+), 48 deletions(-) diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c index 8d9d95c..a33edc3 100644 --- a/src/modules/bluetooth/backend-native.c +++ b/src/modules/bluetooth/backend-native.c @@ -44,9 +44,11 @@ struct pa_bluetooth_backend { PA_LLIST_HEAD(pa_dbus_pending, pending); }; -struct transport_rfcomm { +struct transport_data { int rfcomm_fd; pa_io_event *rfcomm_io; +int sco_fd; +pa_io_event *sco_io; pa_mainloop_api *mainloop; }; @@ -59,6 +61,9 @@ struct transport_rfcomm { #define BLUEZ_PROFILE_INTERFACE BLUEZ_SERVICE ".Profile1" #define HSP_AG_PROFILE "/Profile/HSPAGProfile" +#define HSP_HS_PROFILE "/Profile/HSPHSProfile" + +#define HSP_HS_DEFAULT_CHANNEL 3 #define PROFILE_INTROSPECT_XML \ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ @@ -100,7 +105,7 @@ static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_backend *backend, D return p; } -static int bluez5_sco_acquire_cb(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu) { +static int sco_do_connect(pa_bluetooth_transport *t) { pa_bluetooth_device *d = t->device; struct sockaddr_sco addr; int err, i; @@ -137,12 +142,51 @@ static int bluez5_sco_acquire_cb(pa_bluetooth_transport *t, bool optional, size_ addr.sco_family = AF_BLUETOOTH; bacpy(&addr.sco_bdaddr, &dst); -pa_log_info ("doing connect\n"); +pa_log_info ("doing connect"); err = connect(sock, (struct sockaddr *) &addr, sizeof(addr)); if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS)) { pa_log_error("connect(): %s", pa_cstrerror(errno)); goto fail_close; } +return sock; + +fail_close: +close(sock); +return -1; +} + +static int sco_do_accept(pa_bluetooth_transport *t) { +struct transport_data *trd = t->userdata; +struct sockaddr_sco addr; +socklen_t optlen; +int sock; + +memset(&addr, 0, sizeof(addr)); +optlen = sizeof(addr); + +pa_log_info ("doing accept"); +sock = accept(trd->sco_fd, (struct sockaddr *) &addr, &optlen); +if (sock < 0) { +if (errno != EAGAIN) +pa_log_error("accept(): %s", pa_cstrerror(errno)); +goto fail; +} +return sock; + +fail: +return -1; +} + +static int bluez5_sco_acquire_cb(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu) { +int sock; + +if (optional) +sock = sco_do_accept(t); +else +sock = sco_do_connect(t); + +if (sock < 0) +goto fail; /* The "48" below is hardcoded until we get meaningful MTU values exposed * by the kernel */ @@ -155,8 +199,7 @@ static int bluez5_sco_acquire_cb(pa_bluetooth_transport *t, bool optional, size_ return sock; -fail_close: -close(sock); +fail: return -1; } @@ -165,6 +208,62 @@ static void bluez5_sco_release_cb(pa_bluetooth_transport *t) { /* device will close the SCO socket for us */ } +static void sco_io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) { +pa_bluetooth_transport *t = userdata; + +pa_assert(io); +pa_assert(t); + +if (events & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) { +pa_log_error("error listening SCO connection: %s", pa_cstrerror(errno)); +goto fail; +} + +pa_log_info("SCO incomming connection: changing state to PLAYING"); +pa_bluetooth_transport_set_state (t, PA_BLUETOOTH_TRANSPORT_STATE_PLAYING); + +fail: +return; +} + +static int bluez5_sco_listen(pa_bluetooth_transport *t) { +struct transport_data *trd = t->userdata; +struct sockaddr_sco addr; +int sock; + +sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, BTPROTO_SCO); +if (sock < 0) { +pa_log_error("socket(SEQPACKET, SCO) %s", pa_cstrerror(errno)); +return -1; +} + +/* Bind
[pulseaudio-discuss] [PATCH] add support for HSP Headset profile
This adds support for the HSP HS role and turns pulseaudio into a Headset. This is an updated patch that adds support for incomming SCO connections from the Audio Gateway. Wim Taymans (1): backend-native: add support for the HSP HeadeSet profile src/modules/bluetooth/backend-native.c | 238 ++- src/modules/bluetooth/module-bluez5-device.c | 32 +++- 2 files changed, 222 insertions(+), 48 deletions(-) -- 2.1.0 ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
[pulseaudio-discuss] [PATCH 0/2] access-control prototype
Hi all, I've been experimenting with adding access control to pulseaudio and I would like to start the discussion and propose some ideas with the attached patch set. With the current pulseaudio policy, once the app has made a connection with the daemon, it can perform any action it wants. This includes inspecting the state of the various objects but more problematically, the client is allowed to change the volume of any stream or sink/source or even kill any stream in the daemon. Any client is also allowed to start capturing from any of the sources or even the monitor sources of other streams. This is not something we want to allow in all cases without explicitly asking the user first. The primary use case for access control is sandboxed apps where we want to restrict the actions that the sandboxed app can perform on pulseaudio. We currently bind mount the pulseaudio socket in the sandbox and let the clients use that to communicate over the native protocol with the pulseaudio daemon [1]. This is the only way to communicate with pulseaudio, so the cli protocol and other protocols are disabled and excluded from this discussion. Pulseaudio was not designed and built with access control in mind from the start. Adding full control at the lowest level is probably not feasable at this stage, and I believe it is not really needed. Instead, I've experimented with blocking access at the protocol-native level [2]. With the native protocol, there are about 39 interesting actions that a client can perform. These roughly map to the pa_context, introspection, sample cache and pa_stream API. The idea is to fire a hook for each of these commands to check if the object being operated on can be seen/modified by the requesting client. The implementation of these checks happens in modules that can implement whatever policy is needed by connecting to the various hooks. This makes for some simple extra additions in protocol-native.c For the subscribed events, we need to be carefull that we don't send events to clients about objects that the client is not allowed to see, like an input_stream of some other client, if the policy does not allow that. It's a little tricky because with the REMOVE event, we can't get to the original object anymore (to check who the owner was, for example). This can be solved by keeping a list of all the events sent to a client about what objects, as is implemented in [2]. There are more actions in the pa_stream object but I believe it is not very useful to control access to those. Those actions are about configuring the stream attributes and get status information about the streams. I believe it is sufficient to allow or deny the creation of the pa_stream and after that, you are free to use all pa_stream actions on that stream. again, moving the stream to another sink is something that can be blocked, if needed because it is not an action on the pa_stream. What we eventually might want to do is ask the user for permission before a potentially dangerous action is done (record from a source, switch sinks/source,...). The idea is to let the access module call into a dbus api that will handle the user interaction and tells us what to do next. We would then also need to store those results in a database so that we don't have to ask for permission every time. I'm not sure we can block those protocol-native calls while waiting for the user or if we have to make them async on the server. [2] contains a simple example access module that has some hardcoded checks on input/output sink/source to see if the streams belong to the client that created them. This essentially makes each client only see its own streams and disallows any action on a stream that's not from the client. The idea is to do different checks based on the user/cgroup/... a process is executed under, possibly configured with a config file somehow. What do you think? I would love to hear your ideas. [1] https://wiki.gnome.org/action/info/Projects/SandboxedApps/Sandbox [2] http://cgit.freedesktop.org/~wtay/pulseaudio/log/?h=access-hooks Wim Taymans (2): access: add access control hooks module-access: make policy object containing rules src/Makefile.am | 8 + src/modules/module-access.c | 354 src/pulsecore/access.h | 98 +++ src/pulsecore/core-subscribe.c | 2 +- src/pulsecore/core.c| 5 + src/pulsecore/core.h| 3 + src/pulsecore/protocol-native.c | 152 + 7 files changed, 621 insertions(+), 1 deletion(-) create mode 100644 src/modules/module-access.c create mode 100644 src/pulsecore/access.h -- 2.1.0 ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
[pulseaudio-discuss] [PATCH 1/2] access: add access control hooks
Add hooks to check if a certain operation is allowed. Make a simple module that implements a simple policy that only allows actions on the client's own sink_input, source_output and client. --- src/Makefile.am | 8 ++ src/modules/module-access.c | 308 src/pulsecore/access.h | 98 + src/pulsecore/core-subscribe.c | 2 +- src/pulsecore/core.c| 5 + src/pulsecore/core.h| 3 + src/pulsecore/protocol-native.c | 152 7 files changed, 575 insertions(+), 1 deletion(-) create mode 100644 src/modules/module-access.c create mode 100644 src/pulsecore/access.h diff --git a/src/Makefile.am b/src/Makefile.am index 67f8627..bd32a20 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -911,6 +911,7 @@ lib_LTLIBRARIES += libpulsecore-@PA_MAJORMINOR@.la # Pure core stuff libpulsecore_@PA_MAJORMINOR@_la_SOURCES = \ + pulsecore/access.h \ pulsecore/asyncmsgq.c pulsecore/asyncmsgq.h \ pulsecore/asyncq.c pulsecore/asyncq.h \ pulsecore/auth-cookie.c pulsecore/auth-cookie.h \ @@ -1139,6 +1140,7 @@ modlibexec_LTLIBRARIES += \ endif modlibexec_LTLIBRARIES += \ + module-access.la \ module-cli.la \ module-cli-protocol-tcp.la \ module-simple-protocol-tcp.la \ @@ -1435,6 +1437,7 @@ endif # These are generated by an M4 script SYMDEF_FILES = \ + module-access-symdef.h \ module-cli-symdef.h \ module-cli-protocol-tcp-symdef.h \ module-cli-protocol-unix-symdef.h \ @@ -1557,6 +1560,11 @@ module_simple_protocol_unix_la_CFLAGS = -DUSE_UNIX_SOCKETS -DUSE_PROTOCOL_SIMPLE module_simple_protocol_unix_la_LDFLAGS = $(MODULE_LDFLAGS) module_simple_protocol_unix_la_LIBADD = $(MODULE_LIBADD) libprotocol-simple.la +# Access control +module_access_la_SOURCES = modules/module-access.c +module_access_la_LDFLAGS = $(MODULE_LDFLAGS) +module_access_la_LIBADD = $(MODULE_LIBADD) + # CLI protocol module_cli_la_SOURCES = modules/module-cli.c diff --git a/src/modules/module-access.c b/src/modules/module-access.c new file mode 100644 index 000..dc04b96 --- /dev/null +++ b/src/modules/module-access.c @@ -0,0 +1,308 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering +2015 Wim Taymans + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "module-access-symdef.h" + +PA_MODULE_AUTHOR("Wim Taymans"); +PA_MODULE_DESCRIPTION("Controls access to server resources"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(true); +PA_MODULE_USAGE(""); + +static const char* const valid_modargs[] = { +NULL, +}; + +typedef struct event_item event_item; + +struct event_item { +PA_LLIST_FIELDS(event_item); + +uint32_t client_index; +int facility; +uint32_t object_index; +}; + +struct userdata { +pa_core *core; +pa_hook_slot *hook[PA_ACCESS_HOOK_MAX]; + +PA_LLIST_HEAD(event_item, events); +}; + +static pa_hook_result_t access_check_owner (pa_core *c, pa_access_data *d, struct userdata *u) { +pa_hook_result_t result = PA_HOOK_STOP; +uint32_t idx = PA_INVALID_INDEX; + +switch (d->hook) { +case PA_ACCESS_HOOK_GET_CLIENT_INFO: +case PA_ACCESS_HOOK_KILL_CLIENT: { +idx = d->object_index; +break; +} + +case PA_ACCESS_HOOK_GET_SINK_INPUT_INFO: +case PA_ACCESS_HOOK_MOVE_SINK_INPUT: +case PA_ACCESS_HOOK_SET_SINK_INPUT_VOLUME: +case PA_ACCESS_HOOK_SET_SINK_INPUT_MUTE: +case PA_ACCESS_HOOK_KILL_SINK_INPUT: { +const pa_sink_input *si = pa_idxset_get_by_index(c->sink_inputs, d->object_index); +idx = (si && si->client) ? si->client->index : PA_INVALID_INDEX; +break; +} + +case PA_ACCESS_HOOK_GET_SOURCE_OUTPUT_INFO: +case PA_ACCESS_HOOK_MOVE_SOURCE_OUTPUT: +case PA_ACCESS_HOOK_SET_SOURCE_O
[pulseaudio-discuss] [PATCH 2/2] module-access: make policy object containing rules
Make a policy object that contains the rules for checking access to resources. The idea is that we can have different policies later and then, depending on the client, use one of the policies. --- src/modules/module-access.c | 184 +++- 1 file changed, 115 insertions(+), 69 deletions(-) diff --git a/src/modules/module-access.c b/src/modules/module-access.c index dc04b96..dd6af7f 100644 --- a/src/modules/module-access.c +++ b/src/modules/module-access.c @@ -52,6 +52,8 @@ static const char* const valid_modargs[] = { }; typedef struct event_item event_item; +typedef struct access_policy access_policy; +typedef struct userdata userdata; struct event_item { PA_LLIST_FIELDS(event_item); @@ -63,13 +65,28 @@ struct event_item { struct userdata { pa_core *core; + pa_hook_slot *hook[PA_ACCESS_HOOK_MAX]; PA_LLIST_HEAD(event_item, events); + +pa_idxset *policies; +uint32_t default_policy; +}; + +typedef bool (*access_rule_t)(pa_core *c, pa_access_data *d, struct userdata *u); + +struct access_policy { +uint32_t index; +struct userdata *userdata; + +access_rule_t rule[PA_ACCESS_HOOK_MAX]; }; -static pa_hook_result_t access_check_owner (pa_core *c, pa_access_data *d, struct userdata *u) { -pa_hook_result_t result = PA_HOOK_STOP; + +/* rule checks if the operation on the object is performed by the owner of the object */ +static bool rule_check_owner (pa_core *c, pa_access_data *d, struct userdata *u) { +bool result = false; uint32_t idx = PA_INVALID_INDEX; switch (d->hook) { @@ -102,13 +119,57 @@ static pa_hook_result_t access_check_owner (pa_core *c, pa_access_data *d, struc break; } if (idx == d->client_index) -result = PA_HOOK_OK; +result = true; else pa_log("blocked operation %d/%d of client %d to client %d", d->hook, d->object_index, idx, d->client_index); return result; } +/* rule allows the operation */ +static bool rule_allow (pa_core *c, pa_access_data *d, struct userdata *u) { +pa_log("allow operation %d/%d for client %d", d->hook, d->object_index, d->client_index); +return true; +} + +/* rule blocks the operation */ +static bool rule_block (pa_core *c, pa_access_data *d, struct userdata *u) { +pa_log("blocked operation %d/%d for client %d", d->hook, d->object_index, d->client_index); +return false; +} + +static access_policy *access_policy_new(struct userdata *u, bool allow_all) { +access_policy *ap; +int i; + +ap = pa_xnew0(access_policy, 1); +ap->userdata = u; +for (i = 0; i < PA_ACCESS_HOOK_MAX; i++) + ap->rule[i] = allow_all ? rule_allow : rule_block; + +pa_idxset_put(u->policies, ap, &ap->index); + +return ap; +} + +static void access_policy_free(access_policy *ap) { +pa_idxset_remove_by_index(ap->userdata->policies, ap->index); +pa_xfree(ap); +} + +static pa_hook_result_t check_access (pa_core *c, pa_access_data *d, struct userdata *u) { +access_policy *ap; +access_rule_t rule; + +ap = pa_idxset_get_by_index(u->policies, u->default_policy); + +rule = ap->rule[d->hook]; +if (rule && rule(c, d, u)) + return PA_HOOK_OK; + +return PA_HOOK_STOP; +} + static const pa_access_hook_t event_hook[PA_SUBSCRIPTION_EVENT_FACILITY_MASK+1] = { [PA_SUBSCRIPTION_EVENT_SINK] = PA_ACCESS_HOOK_GET_SINK_INFO, [PA_SUBSCRIPTION_EVENT_SOURCE] = PA_ACCESS_HOOK_GET_SOURCE_INFO, @@ -153,6 +214,7 @@ static bool remove_event(struct userdata *u, uint32_t cidx, int facility, uint32 return false; } + static pa_hook_result_t filter_event (pa_core *c, pa_access_data *d, struct userdata *u) { int facility; @@ -195,47 +257,11 @@ static pa_hook_result_t filter_event (pa_core *c, pa_access_data *d, struct user return PA_HOOK_STOP; } -static pa_hook_result_t access_block (pa_core *c, pa_access_data *d, struct userdata *u) { -pa_log("blocked operation %d/%d for client %d", d->hook, d->object_index, d->client_index); -return PA_HOOK_STOP; -} - -static void install_cb(struct userdata *u, pa_access_hook_t id, pa_hook_cb_t cb) { -if (u->hook[id]) -pa_hook_slot_free(u->hook[id]); -if (cb) -u->hook[id] = pa_hook_connect(&u->core->access[id], PA_HOOK_EARLY - 1, cb, u); -else -u->hook[id] = NULL; -} - -static void allow(struct userdata *u, pa_access_hook_t id) { -install_cb(u, id, NULL); -} - -static void block(struct userdata *u, pa_access_hook_t id) { -install_cb(u, id, (pa_hook_cb_t) access_block); -} - -static void check_owner(struct userdata *u, pa_access_hook_t id) { -install_cb(u, id, (pa_hook_cb_t) access_check_owner); -} - -static void allow_all(struct userdata *u) { -int i; -for (i = 0; i < PA_ACCESS_HOOK_MAX; i++) -allow(u, i); -} - -static void block_all(struct userdata *u) { -int i; -for (i = 0; i < PA_ACCESS_HOOK_MAX; i++) -block(u, i); -}
[pulseaudio-discuss] Fwd: [PATCH 0/2] access-control prototype
And to the list.. -- Forwarded message -- From: Wim Taymans Date: 18 March 2015 at 09:34 Subject: Re: [pulseaudio-discuss] [PATCH 0/2] access-control prototype To: Tanu Kaskinen On 17 March 2015 at 20:37, Tanu Kaskinen wrote: > > No, I don't think it's possible to keep the main thread blocked while > waiting for input from the user. > > We discussed this topic a bit in IRC today, and I got the impression > that you and Arun have already experimented a bit with asking the user > whether certain operations should be authorized. Arun told that instead > of having just one asynchronous call to the policy module, it would be > easier to implement one synchronous call for "basic checks" and if the > basic checks fail, then an asynchronous call would be made for "full > checks" (interactive). These patches don't contain either approach (only > synchronous hooks are used), and I have trouble understanding what the > problem is and how does introducing one extra call help in any way. > Could you or Arun clarify? > The problem is that we might have to ask the user for permission and that we then would block the protocol-native command and thus also the main-loop. We need a way to get back to the main-loop while waiting for the user input and we explored 3 ideas: - manually run the main-loop when waiting for user-input. We were not sure if this is something the pulse mainloop would support so we didn't investigate this further. - make all commands support async replies. For this we would have to change all command handlers to store their variables in a little struct, then call the access hook with that struct. When the reply is ready to be sent we would call the function to generate the reply. This is not very complicated but it would result in more code to review. - do synchronous calls to the access module and add a new command that would allow async replies on the server and could thus be used to ask for user input. So option 3 is what we're currently looking at in more detail. The idea is that those hooks proposed in the patches, don't block but simply perform a lookup into the database to decide if things are allowed or refused. When a command is not allowed, a client simply gets an PA_ERR_ACCESS reply from the server. This would then trigger our new AUTHORIZATION request for the failed action. This new request would be implemented in such a way that the access module can reply in an asynchronous matter (something like a context + finish method is given to the hook, when the hook has the reply, it calls the finish method to send the reply to the client). This new request would be called automatically by the client library to give all clients the ability to ask for authorization. The advantage of this new command is that we only have to add support for async replies to this new command. The disadvantage is that we need another round trip and some overhead in checking permissions twice. The only reason for this new command would be because we can then avoid making all command async on the server. Maybe it is worth making a prototype of option 2 (maybe only for some commands) to get an idea of the changes that are needed. Wim > > -- > Tanu > > ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
[pulseaudio-discuss] [PATCH] Add korean translation
This is a patch to add korean translation that I keep around in RHEL7 but there is no reason to not add this upstream. Translation are for 3.0 of pulseaudio. Wim Taymans (1): Add korean translation po/LINGUAS |1 + po/ko.po | 3012 2 files changed, 3013 insertions(+) create mode 100644 po/ko.po -- 2.1.0 ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
[pulseaudio-discuss] [PATCH] Add korean translation
--- po/LINGUAS |1 + po/ko.po | 3012 2 files changed, 3013 insertions(+) create mode 100644 po/ko.po diff --git a/po/LINGUAS b/po/LINGUAS index 71c73a5..58e2bf8 100644 --- a/po/LINGUAS +++ b/po/LINGUAS @@ -16,6 +16,7 @@ id it ja kn +ko ml mr nl diff --git a/po/ko.po b/po/ko.po new file mode 100644 index 000..9fd5940 --- /dev/null +++ b/po/ko.po @@ -0,0 +1,3012 @@ +# eukim , 2013. #zanata +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2013-08-06 14:45+0530\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"PO-Revision-Date: 2013-10-20 10:38-0400\n" +"Last-Translator: eukim \n" +"Language-Team: Korean\n" +"Language: ko\n" +"X-Generator: Zanata 3.1.2\n" +"Plural-Forms: nplurals=1; plural=0\n" + +#: ../src/modules/alsa/alsa-util.c:1128 ../src/modules/alsa/alsa-util.c:1203 +#, c-format +msgid "" +"snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " +"ms).\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " +"to the ALSA developers." +msgstr "" +"snd_pcm_avail()이 %lu 바이트 (%lu ms)의 매우 큰 값을 반환했습니다.\n" +"ALSA 드라이버 '%s'의 오류일 수 있습니다. ALSA 개발자에게 이 문제를 보고해주시기 바랍니다." + +#: ../src/modules/alsa/alsa-util.c:1178 +#, c-format +msgid "" +"snd_pcm_delay() returned a value that is exceptionally large: %li bytes " +"(%s%lu ms).\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " +"to the ALSA developers." +msgstr "" +"snd_pcm_delay()가 %li 바이트 (%s%lu ms)의 매우 큰 값을 반환했습니다.\n" +"ALSA 드라이버 '%s'의 오류일 수 있습니다. ALSA 개발자에게 이 문제를 보고해주시기 바랍니다." + +#: ../src/modules/alsa/alsa-util.c:1219 +#, c-format +msgid "" +"snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " +"%lu.\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " +"to the ALSA developers." +msgstr "" +"snd_pcm_avail_delay()가 이상한 값을 반환했습니다: 지연 시간 %lu은 사용 가능한 시간 %lu 보다 작습니다.\n" +"ALSA 드라이버 '%s'의 오류일 수 있습니다. ALSA 개발자에게 이 문제를 보고해 주시기 바랍니다." + +#: ../src/modules/alsa/alsa-util.c:1262 +#, c-format +msgid "" +"snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " +"(%lu ms).\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " +"to the ALSA developers." +msgstr "" +"snd_pcm_mmap_begin()이 %lu 바이트 (%lu ms)의 매우 큰 값을 반환했습니다.\n" +"ALSA 드라이버 '%s'의 오류일 수 있습니다. ALSA 개발자에게 이 문제를 보고해 주시기 바랍니다." + +#: ../src/modules/module-always-sink.c:38 +msgid "Always keeps at least one sink loaded even if it's a null one" +msgstr "빈 싱크를 포함하여 최소한 하나 이상의 싱크가 존재해야 합니다." + +#: ../src/modules/module-always-sink.c:82 +msgid "Dummy Output" +msgstr "가짜 출력" + +#: ../src/modules/module-ladspa-sink.c:53 +msgid "Virtual LADSPA sink" +msgstr "가상 LADSPA 싱크" + +#: ../src/modules/module-ladspa-sink.c:57 +msgid "" +"sink_name= sink_properties= " +"master= format= rate= " +"channels= channel_map= plugin= label= control= input_ladspaport_map= output_ladspaport_map= " +msgstr "" +"sink_name= sink_properties= " +"master= format= rate= " +"channels= channel_map= plugin= label= control= input_ladspaport_map= output_ladspaport_map= " + +#: ../src/modules/module-null-sink.c:49 +msgid "Clocked NULL sink" +msgstr "클럭 사용 빈 싱크" + +#: ../src/modules/module-null-sink.c:280 +msgid "Null Output" +msgstr "빈 출력" + +#: ../src/pulsecore/sink.c:3416 +msgid "Built-in Audio" +msgstr "내장 오디오 " + +#: ../src/pulsecore/sink.c:3421 +msgid "Modem" +msgstr "모뎀 " + +#: ../src/daemon/ltdl-bind-now.c:127 +msgid "Failed to find original lt_dlopen loader." +msgstr "기존 lt_dlopen 로더를 찾는데 실패했습니다." + +#: ../src/daemon/ltdl-bind-now.c:132 +msgid "Failed to allocate new dl loader." +msgstr "새 dl 로더를 할당하는데 실패했습니다." + +#: ../src/daemon/ltdl-bind-now.c:145 +msgid "Failed to add bind-now-loader." +msgstr "bind-now-loader를 추가하는데 실패했습니다." + +#: ../src/daemon/main.c:139 +#, c-format +msgid "Got signal %s." +msgstr "시그널 %s를 받았습니다." + +#: ../src/daemon/main.c:166 +msgid "Exiting." +msgstr "종료합니다." + +#: ../src/daemon/main.c:184 +#, c-format +msgid "Failed to find user '%s'." +msgstr "사용자 '%s'를 찾을 수 없습니다." + +#: ../src/daemon/main.c:189 +#, c-format +msgid "Failed to find group '%s'." +msgstr "그룹 '%s'를 찾을 수 없습니다." + +#: ../src/daemon/main.c:193 +#, c-format +msgid "Found user '%s' (UID %lu) and group '%s' (GID %lu)." +msgstr "사용자 \"%s' (UID %lu)와 그룹 '%s' (GID %lu)를 찾았습니다." + +#: ../src/daemon/main.c:198 +#, c-format +msgid "GID of user '%s' and of group '%s' don't match." +msgstr "사용자 '%s'의 GID와 그룹 '%s'가 일치하지 않습니다." + +#: ../src/daemon/main.c:203 +#, c-format +msgid "Home directory of user '%s' is not '%s', ignoring." +msgstr "사용자 '%s'의 홈 디렉토리가 '%s'가 아닙니다, 무시됨." + +#: ../src/daemon/main.c:206 ../src/daemon/main.c:211 +#, c-format +msgid "Failed to create '%s': %s" +msgstr "'%s' 생성 실패: %s" + +#: ../src/daemon/main.c:218 +#, c-format +msgid "Failed to change group list: %s" +msgstr "그
[pulseaudio-discuss] [PATCH 4/5] protocol-native: add access checks
Call the hooks in various places to check if an action is allowed or not. The hook can return PA_HOOK_OK to allow an action, PA_HOOK_STOP to deny an action. Returning PA_HOOK_CANCEL will handle the result asynchronously. This is implemented by saving the state of the current command and exiting the command (going back to the mainloop) without reply. When the access check completes, it will call async_finish_cb, which will use the saved state to call the original command again. The access check will eventually return OK or STOP and the command can complete and send a reply. --- src/pulsecore/protocol-native.c | 219 +++- 1 file changed, 217 insertions(+), 2 deletions(-) diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c index b06f553..f23de31 100644 --- a/src/pulsecore/protocol-native.c +++ b/src/pulsecore/protocol-native.c @@ -294,6 +294,7 @@ static void command_set_card_profile(pa_pdispatch *pd, uint32_t command, uint32_ static void command_set_sink_or_source_port(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); static void command_set_port_latency_offset(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); static void command_enable_srbchannel(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static pa_hook_result_t check_access(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata, uint32_t idx, pa_subscription_event_type_t event, const char *name); static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = { [PA_COMMAND_ERROR] = NULL, @@ -1972,6 +1973,23 @@ if (!(expression)) { \ } \ } while(0); +#define CHECK_ACCESS_STMT(c, command, tag, idx, name, async, denied) { \ + pa_hook_result_t res = check_access(pd, command, tag, t, userdata, idx, 0, name); \ + if (res == PA_HOOK_STOP) { \ + denied; \ + } else if (res == PA_HOOK_CANCEL) { \ + async; \ + } \ +}; + +#define CHECK_ACCESS_GOTO(c, command, tag, idx, name, label) \ +CHECK_ACCESS_STMT(c, command, tag, idx, name, \ +goto label, pa_pstream_send_error(c->pstream, tag, PA_ERR_ACCESS); goto label) + +#define CHECK_ACCESS(c, command, tag, idx, name) \ +CHECK_ACCESS_STMT(c, command, tag, idx, name, \ +return, pa_pstream_send_error(c->pstream, tag, PA_ERR_ACCESS); return) + static pa_tagstruct *reply_new(uint32_t tag) { pa_tagstruct *reply; @@ -2049,6 +2067,8 @@ static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, u CHECK_VALIDITY_GOTO(c->pstream, !sink_name || sink_index == PA_INVALID_INDEX, tag, PA_ERR_INVALID, finish); CHECK_VALIDITY_GOTO(c->pstream, pa_cvolume_valid(&volume), tag, PA_ERR_INVALID, finish); +CHECK_ACCESS_GOTO(c, command, tag, PA_INVALID_INDEX, NULL, finish); + p = pa_proplist_new(); if (name) @@ -2370,6 +2390,8 @@ static void command_create_record_stream(pa_pdispatch *pd, uint32_t command, uin CHECK_VALIDITY_GOTO(c->pstream, source_index == PA_INVALID_INDEX || !source_name, tag, PA_ERR_INVALID, finish); CHECK_VALIDITY_GOTO(c->pstream, !source_name || source_index == PA_INVALID_INDEX, tag, PA_ERR_INVALID, finish); +CHECK_ACCESS_GOTO(c, command, tag, PA_INVALID_INDEX, NULL, finish); + p = pa_proplist_new(); if (name) @@ -2575,6 +2597,7 @@ static void command_exit(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_ta } CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); +CHECK_ACCESS(c, command, tag, PA_INVALID_INDEX, NULL); ret = pa_core_exit(c->protocol->core, false, 0); CHECK_VALIDITY(c->pstream, ret >= 0, tag, PA_ERR_ACCESS); @@ -2893,6 +2916,7 @@ static void command_stat(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_ta } CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); +CHECK_ACCESS(c, command, tag, PA_INVALID_INDEX, NULL); stat = pa_mempool_get_stat(c->protocol->core->mempool); @@ -3019,6 +3043,8 @@ static void command_create_upload_stream(pa_pdispatch *pd, uint32_t command, uin CHECK_VALIDITY(c->pstream, (length % pa_frame_size(&ss)) == 0 && length > 0, tag, PA_ERR_INVALID); CHECK_VALIDITY(c->pstream, length <= PA_SCACHE_ENTRY_SIZE_MAX, tag, PA_ERR_TOOLARGE); +CHECK_ACCESS(c, command, tag, PA_INVALID_INDEX, NULL); + p = pa_proplist_new(); if ((c->version >= 13 && pa_tagstruct_get_proplist(t, p) < 0) || @@ -3117,6 +3143,8 @@ static void command_play_sample(pa_pdispatch *pd, uint32_t command, uint32_t tag CHECK_VALIDITY(c->pstream, sink, tag, PA_ERR_NOENTITY); +CHECK_ACCESS(c, command, tag, sink->index, name); + p = pa_proplist_new(); if ((c->version >= 13 && pa_tagstruct_get_proplist(t, p) < 0) || @@ -3160,6 +3188,8 @@ static void command_remove_sample(pa_pdispatch *pd, uint32_t command, uint32_t t CHECK_VALIDITY(c->pstream, c->authori
[pulseaudio-discuss] [PATCH 2/5] subscribe: fix typo
--- src/pulsecore/core-subscribe.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pulsecore/core-subscribe.c b/src/pulsecore/core-subscribe.c index 61c779b..5a88b7e 100644 --- a/src/pulsecore/core-subscribe.c +++ b/src/pulsecore/core-subscribe.c @@ -206,7 +206,7 @@ void pa_subscription_post(pa_core *c, pa_subscription_event_type_t t, uint32_t i pa_subscription_event *e; pa_assert(c); -/* No need for queuing subscriptions of no one is listening */ +/* No need for queuing subscriptions if no one is listening */ if (!c->subscriptions) return; -- 2.1.0 ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
[pulseaudio-discuss] [PATCH 5/5] module-access: add example access module
Add an example access module that only allows access to the objects created by the owner client. The code is structured in such a way that multiple profiles can be made and that a profile can be activated based on the properties of a client. Events need special handling, we don't want to send events to clients about objects they are not allowed to see. We need to be careful because we can't inspect the owner of an object anymore in a _REMOVE event for that object. We fix this by keeping a list of all the objects for which we were allowed to notify a CHANGE or NEW event. We then only send REMOVE events to objects from this list. --- src/Makefile.am | 7 + src/modules/module-access.c | 479 2 files changed, 486 insertions(+) create mode 100644 src/modules/module-access.c diff --git a/src/Makefile.am b/src/Makefile.am index 0813285..8b4ed31 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1149,6 +1149,7 @@ modlibexec_LTLIBRARIES += \ endif modlibexec_LTLIBRARIES += \ + module-access.la \ module-cli.la \ module-cli-protocol-tcp.la \ module-simple-protocol-tcp.la \ @@ -1445,6 +1446,7 @@ endif # These are generated by an M4 script SYMDEF_FILES = \ + module-access-symdef.h \ module-cli-symdef.h \ module-cli-protocol-tcp-symdef.h \ module-cli-protocol-unix-symdef.h \ @@ -1567,6 +1569,11 @@ module_simple_protocol_unix_la_CFLAGS = -DUSE_UNIX_SOCKETS -DUSE_PROTOCOL_SIMPLE module_simple_protocol_unix_la_LDFLAGS = $(MODULE_LDFLAGS) module_simple_protocol_unix_la_LIBADD = $(MODULE_LIBADD) libprotocol-simple.la +# Access control +module_access_la_SOURCES = modules/module-access.c +module_access_la_LDFLAGS = $(MODULE_LDFLAGS) +module_access_la_LIBADD = $(MODULE_LIBADD) + # CLI protocol module_cli_la_SOURCES = modules/module-cli.c diff --git a/src/modules/module-access.c b/src/modules/module-access.c new file mode 100644 index 000..2d1ec9f --- /dev/null +++ b/src/modules/module-access.c @@ -0,0 +1,479 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + 2015 Wim Taymans + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "module-access-symdef.h" + +PA_MODULE_AUTHOR("Wim Taymans"); +PA_MODULE_DESCRIPTION("Controls access to server resources"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(true); +PA_MODULE_USAGE(""); + +static const char* const valid_modargs[] = { +NULL, +}; + +typedef struct access_policy access_policy; +typedef struct event_item event_item; +typedef struct client_data client_data; +typedef struct userdata userdata; + +typedef pa_hook_result_t (*access_rule_t)(pa_core *c, pa_access_data *d, struct userdata *u); + +struct access_policy { +uint32_t index; +struct userdata *userdata; + +access_rule_t rule[PA_ACCESS_HOOK_MAX]; +}; + +struct event_item { +PA_LLIST_FIELDS(event_item); + +int facility; +uint32_t object_index; +}; +struct client_data { +uint32_t index; +uint32_t policy; + +PA_LLIST_HEAD(event_item, events); +}; + +struct userdata { +pa_core *core; + +pa_hook_slot *hook[PA_ACCESS_HOOK_MAX]; + +pa_idxset *policies; +uint32_t default_policy; + +pa_hashmap *clients; +pa_hook_slot *client_put_slot; +pa_hook_slot *client_proplist_changed_slot; +pa_hook_slot *client_unlink_slot; +}; + +static void add_event(struct client_data *cd, int facility, uint32_t oidx) { +event_item *i; + +i = pa_xnew0(event_item, 1); +PA_LLIST_INIT(event_item, i); +i->facility = facility; +i->object_index = oidx; + +PA_LLIST_PREPEND(event_item, cd->events, i); +} + +static event_item *find_event(struct client_data *cd, int facility, uint32_t oidx) { +event_item *i; + +PA_LLIST_FOREACH(i, cd->events) { +if (i->facility == facility && i->object_index == oidx) +return i; +} +return NULL; +} + +static bool remove_event(struct client_data *cd,
[pulseaudio-discuss] [PATCH 0/5] Add access control to protocol-native
This is a new patch series that preplaces the previous patches regarding access control. After some experimentation, this patch series adds support for async access control checks, like when we need to ask for user input. The async support is implemented by exiting the command without reply and then when the reply becomes available, re-execute the command. There is an example access control module that shows how one could implement client specific access control checks. The patch also contains a small typo fix that can probably be applied independently. Wim Taymans (5): tagstruct: add copy method subscribe: fix typo access: Add access control hooks protocol-native: add access checks module-access: add example access module src/Makefile.am | 8 + src/modules/module-access.c | 479 src/pulsecore/access.h | 102 + src/pulsecore/core-subscribe.c | 2 +- src/pulsecore/core.c| 5 + src/pulsecore/core.h| 3 + src/pulsecore/protocol-native.c | 219 +- src/pulsecore/tagstruct.c | 13 ++ src/pulsecore/tagstruct.h | 2 + 9 files changed, 830 insertions(+), 3 deletions(-) create mode 100644 src/modules/module-access.c create mode 100644 src/pulsecore/access.h -- 2.1.0 ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
[pulseaudio-discuss] [PATCH 3/5] access: Add access control hooks
Add hooks to core to check if certain operations are allowed. --- src/Makefile.am| 1 + src/pulsecore/access.h | 102 + src/pulsecore/core.c | 5 +++ src/pulsecore/core.h | 3 ++ 4 files changed, 111 insertions(+) create mode 100644 src/pulsecore/access.h diff --git a/src/Makefile.am b/src/Makefile.am index d582e57..0813285 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -920,6 +920,7 @@ libpulsecore_@PA_MAJORMINOR@_la_SOURCES = \ pulsecore/filter/lfe-filter.c pulsecore/filter/lfe-filter.h \ pulsecore/filter/biquad.c pulsecore/filter/biquad.h \ pulsecore/filter/crossover.c pulsecore/filter/crossover.h \ + pulsecore/access.h \ pulsecore/asyncmsgq.c pulsecore/asyncmsgq.h \ pulsecore/asyncq.c pulsecore/asyncq.h \ pulsecore/auth-cookie.c pulsecore/auth-cookie.h \ diff --git a/src/pulsecore/access.h b/src/pulsecore/access.h new file mode 100644 index 000..7cd2751 --- /dev/null +++ b/src/pulsecore/access.h @@ -0,0 +1,102 @@ +#ifndef fooaccesshfoo +#define fooaccesshfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering +2015 Wim Taymans + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include + +#include + +typedef enum pa_access_hook { +/* context */ +PA_ACCESS_HOOK_EXIT_DAEMON, +PA_ACCESS_HOOK_SET_DEFAULT_SINK, +PA_ACCESS_HOOK_SET_DEFAULT_SOURCE, + +/* introspection */ +PA_ACCESS_HOOK_GET_SINK_INFO, +PA_ACCESS_HOOK_SET_SINK_VOLUME, +PA_ACCESS_HOOK_SET_SINK_MUTE, +PA_ACCESS_HOOK_SUSPEND_SINK, +PA_ACCESS_HOOK_SET_SINK_PORT, + +PA_ACCESS_HOOK_GET_SOURCE_INFO, +PA_ACCESS_HOOK_SET_SOURCE_VOLUME, +PA_ACCESS_HOOK_SET_SOURCE_MUTE, +PA_ACCESS_HOOK_SUSPEND_SOURCE, +PA_ACCESS_HOOK_SET_SOURCE_PORT, + +PA_ACCESS_HOOK_GET_SERVER_INFO, + +PA_ACCESS_HOOK_GET_MODULE_INFO, +PA_ACCESS_HOOK_LOAD_MODULE, +PA_ACCESS_HOOK_UNLOAD_MODULE, + +PA_ACCESS_HOOK_GET_CLIENT_INFO, +PA_ACCESS_HOOK_KILL_CLIENT, + +PA_ACCESS_HOOK_GET_CARD_INFO, +PA_ACCESS_HOOK_SET_CARD_PROFILE, +PA_ACCESS_HOOK_SET_PORT_LATENCY_OFFSET, + +PA_ACCESS_HOOK_GET_SINK_INPUT_INFO, +PA_ACCESS_HOOK_MOVE_SINK_INPUT, +PA_ACCESS_HOOK_SET_SINK_INPUT_VOLUME, +PA_ACCESS_HOOK_SET_SINK_INPUT_MUTE, +PA_ACCESS_HOOK_KILL_SINK_INPUT, + +PA_ACCESS_HOOK_GET_SOURCE_OUTPUT_INFO, +PA_ACCESS_HOOK_MOVE_SOURCE_OUTPUT, +PA_ACCESS_HOOK_SET_SOURCE_OUTPUT_VOLUME, +PA_ACCESS_HOOK_SET_SOURCE_OUTPUT_MUTE, +PA_ACCESS_HOOK_KILL_SOURCE_OUTPUT, + +PA_ACCESS_HOOK_STAT, + +PA_ACCESS_HOOK_GET_SAMPLE_INFO, +/* sample cache */ +PA_ACCESS_HOOK_CONNECT_UPLOAD, +PA_ACCESS_HOOK_REMOVE_SAMPLE, +PA_ACCESS_HOOK_PLAY_SAMPLE, +/* stream */ +PA_ACCESS_HOOK_CONNECT_PLAYBACK, +PA_ACCESS_HOOK_CONNECT_RECORD, +/* extension */ +PA_ACCESS_HOOK_EXTENSION, + +PA_ACCESS_HOOK_FILTER_SUBSCRIBE_EVENT, + +PA_ACCESS_HOOK_MAX +} pa_access_hook_t; + +typedef struct pa_access_data pa_access_data; + +struct pa_access_data { +pa_access_hook_t hook; +uint32_t client_index; +uint32_t object_index; +pa_subscription_event_type_t event; +const char *name; + +void (*async_finish_cb) (pa_access_data *data, bool res); +}; + +#endif diff --git a/src/pulsecore/core.c b/src/pulsecore/core.c index 0e63bac..9135311 100644 --- a/src/pulsecore/core.c +++ b/src/pulsecore/core.c @@ -151,6 +151,9 @@ pa_core* pa_core_new(pa_mainloop_api *m, bool shared, size_t shm_size) { for (j = 0; j < PA_CORE_HOOK_MAX; j++) pa_hook_init(&c->hooks[j], c); +for (j = 0; j < PA_ACCESS_HOOK_MAX; j++) +pa_hook_init(&c->access[j], c); + pa_random(&c->cookie, sizeof(c->cookie)); #ifdef SIGPIPE @@ -222,6 +225,8 @@ static void core_free(pa_object *o) { for (j = 0; j < PA_CORE_HOOK_MAX; j++) pa_hook_done(&c->hooks[j]); +for (j = 0; j < PA_ACCESS_HOOK_MAX; j++) +pa_hook_done(&c->access[j]); pa_xfree(c); } diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h index 9fefd1b..470b821 100644 --- a/src/pulsecore/core.h +++ b/src/pulsecore/core.h @@ -50,6 +50,7 @@ typedef enum pa_suspend_cause { #i
[pulseaudio-discuss] [PATCH 1/5] tagstruct: add copy method
Add a method to copy a tagstruct. This will also reset the read pointer back to the beginning. --- src/pulsecore/tagstruct.c | 13 + src/pulsecore/tagstruct.h | 2 ++ 2 files changed, 15 insertions(+) diff --git a/src/pulsecore/tagstruct.c b/src/pulsecore/tagstruct.c index 8a29957..c29e49b 100644 --- a/src/pulsecore/tagstruct.c +++ b/src/pulsecore/tagstruct.c @@ -97,6 +97,19 @@ void pa_tagstruct_free(pa_tagstruct*t) { pa_xfree(t); } +pa_tagstruct *pa_tagstruct_copy(pa_tagstruct*t) { +pa_tagstruct*tc; + +if (!(tc = pa_flist_pop(PA_STATIC_FLIST_GET(tagstructs +tc = pa_xnew(pa_tagstruct, 1); +tc->data = pa_xmemdup(t->data, t->length); +tc->allocated = t->length; +tc->rindex = 0; +tc->type = PA_TAGSTRUCT_DYNAMIC; + +return tc; +} + static inline void extend(pa_tagstruct*t, size_t l) { pa_assert(t); pa_assert(t->type != PA_TAGSTRUCT_FIXED); diff --git a/src/pulsecore/tagstruct.h b/src/pulsecore/tagstruct.h index 348c65d..e648d75 100644 --- a/src/pulsecore/tagstruct.h +++ b/src/pulsecore/tagstruct.h @@ -64,6 +64,8 @@ pa_tagstruct *pa_tagstruct_new(void); pa_tagstruct *pa_tagstruct_new_fixed(const uint8_t* data, size_t length); void pa_tagstruct_free(pa_tagstruct*t); +pa_tagstruct *pa_tagstruct_copy(pa_tagstruct*t); + int pa_tagstruct_eof(pa_tagstruct*t); const uint8_t* pa_tagstruct_data(pa_tagstruct*t, size_t *l); -- 2.1.0 ___ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss