jnqnfe / pulse-binding-rust Goto Github PK
View Code? Open in Web Editor NEWFFI and bindings for using PulseAudio from the Rust programming language.
License: Apache License 2.0
FFI and bindings for using PulseAudio from the Rust programming language.
License: Apache License 2.0
I cloned the repo, this is what I get on cargo test
:
---- src/mainloop/standard.rs - mainloop::standard (line 57) stdout ----
error: linking with `cc` failed: exit code: 1
|
= note: "cc" "-Wl,--as-needed" "-Wl,-z,noexecstack" "-m64" "-L" "/home/mozgiii/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib" "/tmp/rustdoctestMs1Q5E/rust_out.rust_out.7rcbfp3g-cgu.0.rcgu.o" "/tmp/rustdoctestMs1Q5E/rust_out.rust_out.7rcbfp3g-cgu.1.rcgu.o" "/tmp/rustdoctestMs1Q5E/rust_out.rust_out.7rcbfp3g-cgu.10.rcgu.o" "/tmp/rustdoctestMs1Q5E/rust_out.rust_out.7rcbfp3g-cgu.11.rcgu.o" "/tmp/rustdoctestMs1Q5E/rust_out.rust_out.7rcbfp3g-cgu.12.rcgu.o" "/tmp/rustdoctestMs1Q5E/rust_out.rust_out.7rcbfp3g-cgu.13.rcgu.o" "/tmp/rustdoctestMs1Q5E/rust_out.rust_out.7rcbfp3g-cgu.14.rcgu.o" "/tmp/rustdoctestMs1Q5E/rust_out.rust_out.7rcbfp3g-cgu.15.rcgu.o" "/tmp/rustdoctestMs1Q5E/rust_out.rust_out.7rcbfp3g-cgu.2.rcgu.o" "/tmp/rustdoctestMs1Q5E/rust_out.rust_out.7rcbfp3g-cgu.3.rcgu.o" "/tmp/rustdoctestMs1Q5E/rust_out.rust_out.7rcbfp3g-cgu.4.rcgu.o" "/tmp/rustdoctestMs1Q5E/rust_out.rust_out.7rcbfp3g-cgu.5.rcgu.o" "/tmp/rustdoctestMs1Q5E/rust_out.rust_out.7rcbfp3g-cgu.6.rcgu.o" "/tmp/rustdoctestMs1Q5E/rust_out.rust_out.7rcbfp3g-cgu.7.rcgu.o" "/tmp/rustdoctestMs1Q5E/rust_out.rust_out.7rcbfp3g-cgu.8.rcgu.o" "/tmp/rustdoctestMs1Q5E/rust_out.rust_out.7rcbfp3g-cgu.9.rcgu.o" "-o" "/tmp/rustdoctestMs1Q5E/rust_out" "/tmp/rustdoctestMs1Q5E/rust_out.33dyzt1ekirinwy8.rcgu.o" "-Wl,--gc-sections" "-pie" "-Wl,-zrelro" "-Wl,-znow" "-nodefaultlibs" "-L" "/home/mozgiii/Desktop/pulse-binding-rust/target/debug/deps" "-L" "/usr/lib/x86_64-linux-gnu" "-L" "/home/mozgiii/Desktop/pulse-binding-rust/target/debug/deps" "-L" "/home/mozgiii/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib" "-Wl,-Bstatic" "/home/mozgiii/Desktop/pulse-binding-rust/target/debug/deps/liblibpulse_binding-fff5c6144e0561bf.rlib" "/home/mozgiii/Desktop/pulse-binding-rust/target/debug/deps/liblibpulse_sys-7a0dcb80d455ec51.rlib" "/home/mozgiii/Desktop/pulse-binding-rust/target/debug/deps/liblibc-e30d7bd1ff40dae2.rlib" "-Wl,--start-group" "/home/mozgiii/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd-e39317eb74365d3c.rlib" "/home/mozgiii/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libpanic_unwind-4d55a38564aae54a.rlib" "/home/mozgiii/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libbacktrace_sys-f8521075e248b627.rlib" "/home/mozgiii/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libunwind-7c91ffdc8da860d3.rlib" "/home/mozgiii/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_demangle-0ad27b9879d551d3.rlib" "/home/mozgiii/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/liblibc-588f18eae3ea58be.rlib" "/home/mozgiii/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/liballoc-4ebf5caee903d98f.rlib" "/home/mozgiii/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_std_workspace_core-8895b32baedb08c6.rlib" "/home/mozgiii/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcore-6a9d233d01acc350.rlib" "-Wl,--end-group" "/home/mozgiii/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcompiler_builtins-851bb3b5f6c4db49.rlib" "-Wl,-Bdynamic" "-l:libpulse.so.0" "-l:libpulse.so.0" "-l:libpulse.so.0" "-l:libpulse.so.0" "-l:libpulse.so.0" "-l:libpulse.so.0" "-l:libpulse.so.0" "-l:libpulse.so.0" "-l:libpulse.so.0" "-l:libpulse.so.0" "-l:libpulse.so.0" "-l:libpulse.so.0" "-l:libpulse.so.0" "-l:libpulse.so.0" "-l:libpulse.so.0" "-l:libpulse.so.0" "-l:libpulse.so.0" "-l:libpulse.so.0" "-l:libpulse.so.0" "-l:libpulse.so.0" "-l:libpulse.so.0" "-l:libpulse.so.0" "-l:libpulse.so.0" "-l:libpulse.so.0" "-l:libpulse.so.0" "-l:libpulse.so.0" "-lutil" "-lutil" "-ldl" "-lrt" "-lpthread" "-lgcc_s" "-lc" "-lm" "-lrt" "-lpthread" "-lutil" "-lutil"
= note: /usr/bin/ld: /home/mozgiii/Desktop/pulse-binding-rust/target/debug/deps/liblibpulse_binding-fff5c6144e0561bf.rlib(libpulse_binding-fff5c6144e0561bf.34aowjpr2n0d3j30.rcgu.o): undefined reference to symbol 'pa_encoding_from_string'
//usr/lib/x86_64-linux-gnu/pulseaudio/libpulsecommon-11.1.so: error adding symbols: DSO missing from command line
collect2: error: ld returned 1 exit status
How can I fix this?
MainloopInner::get_api
dereferences the user accessible raw pointer MainloopInner::api
.
This is unsound because a user could safely modify MainloopInner::api
and call MainloopInner::get_api
to cause UB.
With the Futures and Async/Await features soon to be stabilized, is it possible to use this library asynchronously with Tokio? If so, could documentation and possibly a feature be added that allows for it so that actions to be taken after a sound is played can be done through future combinators? I haven't really dug into the library, but it didn't give us compilation issues, so we went with it for a hackathon. We'd like to integrate it into our production application, but we use a Tokio based framework and having this work with that natively would be really useful.
In the Warning
Section:
...
it does fit perfectly with the Rust borrow checking mechanism
and
thus you cannot rely upon the borrow checker to prevent unsafe use as much as is typical.
It fits with the borrow checking mechanism but because of that, you can't rely on the borrow checker?
That seems a bit contradictory.
I'm working with the Pulse Audio binding to do a simple FFT visualizer. I'm getting the following traceback when trying to compile basic usage of the standard mainloop and context and stream stuff:
Compiling libpulse-binding v2.2.0
error[E0308]: mismatched types
--> /Users/noahbkim/.cargo/registry/src/github.com-1ecc6299db9ec823/libpulse-binding-2.2.0/src/time/mod.rs:55:35
|
55 | Timeval::new(secs as i64, usecs as i64)
| ^^^^^^^^^^^^ expected i32, found i64
error[E0308]: mismatched types
--> /Users/noahbkim/.cargo/registry/src/github.com-1ecc6299db9ec823/libpulse-binding-2.2.0/src/time/mod.rs:74:42
|
74 | Timeval::new(t.as_secs() as i64, (t.subsec_nanos() / NANOS_PER_MILLI) as i64)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected i32, found i64
error[E0308]: mismatched types
--> /Users/noahbkim/.cargo/registry/src/github.com-1ecc6299db9ec823/libpulse-binding-2.2.0/src/time/timeval.rs:100:41
|
100 | true => { self.0.tv_usec |= PA_TIMEVAL_RTCLOCK; },
| ^^^^^^^^^^^^^^^^^^ expected i32, found i64
error[E0277]: no implementation for `i32 |= i64`
--> /Users/noahbkim/.cargo/registry/src/github.com-1ecc6299db9ec823/libpulse-binding-2.2.0/src/time/timeval.rs:100:38
|
100 | true => { self.0.tv_usec |= PA_TIMEVAL_RTCLOCK; },
| ^^ no implementation for `i32 |= i64`
|
= help: the trait `std::ops::BitOrAssign<i64>` is not implemented for `i32`
I'm working on the current version of macOS with a vanilla installation of Rust. Let me know if you need any other information.
Having fixed Travis builds to actually run the tests (cargo test
needed --all
) and fixed the first problem - missing package dependency - now we're getting failures on two tests that pass perfectly fine on my local system.
Unfortunately the build script (and actually the local run) is pointing me at line numbers which make no sense - line #41 of src/mainloop/standard.rs and line #213 of src/mainloop/threaded.rs
With no idea where the problem is actually occurring, how to solve (without flooding Travis with attempts at pinpointing the real failure locations)...?
Any ideas anyone?
Hi! I'm trying to create a volume mixer in Rust using your library, and I'm having an issue related to monitor streams. I'm trying to create a peak monitor, roughly derived from pavucontrol's C++ implementation, and as far as I can tell I've inputted the same properties faithfully to libpulse_binding, but the callback I supply to set_read_callback
never triggers. None of the unwrap calls fail, and the program doesn't crash, I just get no response. I've tried querying synchronously using readable_size
as well, and I only ever get None
.
Side note -- when I was trying to debug I ran across Stream::set_state_callback
, and I'm unsure how I'm actually supposed to use it. It doesn't pass the current state into the callback and I can't reference the stream from inside the callback as it's already being used outside of it, so I wasn't able to use it to help me test.
I'm a new Rust programmer, so if I'm making a simple mistake I'd really appreciate being pointed in the right direction. Thank you for your time, and this library!
// These values are dynamic in the actual code.
let input_index = 1;
let sink_index = 0;
let mut attr = BufferAttr::default();
attr.fragsize = 4;
attr.maxlength = u32::MAX;
let spec = Spec { channels: 1, format: Format::F32le, rate: 25 };
assert!(spec.is_valid());
// self.context is an Rc<RefCell<...>> of the context.
let mut s = Stream::new(&mut self.context.borrow_mut(), "Peak Detect", &spec, None).unwrap();
s.set_monitor_stream(index).unwrap();
let tx = self.channel.tx.clone();
s.set_read_callback(Some(Box::new(move |peak| println!("Peak! {}", peak))));
s.connect_record(Some(&sink_index.to_string().as_str()), Some(&attr),
StreamFlagSet::DONT_MOVE | StreamFlagSet::ADJUST_LATENCY | StreamFlagSet::PEAK_DETECT).unwrap();
I'm on Ubuntu 18.04 which comes with PulseAudio 11 as best as I can tell. I'm trying to write something that uses pulse-binding-simple
, but I can't work out how to disable the pa_encoding_from_string
feature which is on by default. I can disable it for pulse-binding
but it seems to be enabled again as soon as I try to use pulse-simple
.
I'm using libpulse-simple-binding 2.14.0 with rustc 1.40 via rustup toolchain install stable-x86_64-pc-windows-gnu
.
Any crate that depends on any crates from this repository where libpulse-sys > 0 is a dependency, fails to build inside libpulse-sys with this toolchain.
Line 17 of libpulse-sys/src/mainloop/standard.rs is use libc::pollfd;
For some reason, this toolchain doesn't include a pollfd
in std
despite the fact that the platform actually supports it in native code. In winsock2.h (from MinGW64):
typedef struct pollfd {
SOCKET fd;
short events;
short revents;
} WSAPOLLFD, *PWSAPOLLFD, *LPWSAPOLLFD;
See also https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsapoll
The expected functions that operate on this struct are easy to emulate. PulseAudio itself has the file poll-win32.c
which implements pa_poll()
using native Win32 API, and the core Pulse library and daemon use pa_poll()
. Unfortunately, Rust just doesn't provide std::pollfd
on Windows.
This is blocking me from being able to get work done, so if you have any idea how to proceed, it would be appreciated.
The Context struct can be connected to an event loop and triggered from a separate thread. Passing in thread-unsafe values here would result in a potential data race, right? So it should probably set the generic bounds Box<dyn FnMut() + 'static + Send>
?
set_state_callback
does not (unlike the C library) supply the Context as a parameter to the closure. That leaves us with passing the Context using an Rc+RefCell to the closure. Which fails at runtime, since the connect
function takes a mutable reference which makes it borrowed twice at the same time when used inside the closure.
I'm aware that with the latest automated monthly CI run the instance using the nightly compiler is now failing, as I can reproduce locally with the nightly compiler.
The following tests are failing:
src/time/microseconds.rs - time::microseconds::MicroSeconds::div_f32 (line 595)
src/time/microseconds.rs - time::microseconds::MicroSeconds::from_secs_f32 (line 257)
src/time/microseconds.rs - time::microseconds::MicroSeconds::from_secs_f64 (line 218)
src/time/microseconds.rs - time::microseconds::MicroSeconds::mul_f32 (line 508)
The error output is as follows:
---- src/time/microseconds.rs - time::microseconds::MicroSeconds::div_f32 (line 595) stdout ----
Test executable failed (exit code 101).
stderr:
thread 'main' panicked at 'assertion failed: `(left == right)`
left: `MicroSeconds(859872558)`,
right: `MicroSeconds(859872559)`', src/time/microseconds.rs:7:1
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
---- src/time/microseconds.rs - time::microseconds::MicroSeconds::from_secs_f32 (line 257) stdout ----
Test executable failed (exit code 101).
stderr:
thread 'main' panicked at 'assertion failed: `(left == right)`
left: `MicroSeconds(2299999)`,
right: `MicroSeconds(2300000)`', src/time/microseconds.rs:6:1
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
---- src/time/microseconds.rs - time::microseconds::MicroSeconds::from_secs_f64 (line 218) stdout ----
Test executable failed (exit code 101).
stderr:
thread 'main' panicked at 'assertion failed: `(left == right)`
left: `MicroSeconds(2299999)`,
right: `MicroSeconds(2300000)`', src/time/microseconds.rs:6:1
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
---- src/time/microseconds.rs - time::microseconds::MicroSeconds::mul_f32 (line 508) stdout ----
Test executable failed (exit code 101).
stderr:
thread 'main' panicked at 'assertion failed: `(left == right)`
left: `MicroSeconds(8478000000)`,
right: `MicroSeconds(8478000152)`', src/time/microseconds.rs:8:1
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
I'll head now to the Rust compiler issue list to file an issue report...
As a new user pulse audio I've been going through the documentation over on the freedesktop site but it's really helpful to see the more Rusty comments and I notice that this crate doesn't have a link to the documents on docs.rs.
This is a shame because the all the necessary doc comments are there they just don't seem to be recognised when doing a cargo publish. I don't know why this is the case, maybe because many of the files begin with /// comments and not //!, but that's just my guess.
I just fried running cargo doc --open
on my cloned repo. It runs fine, so I've no idea why cargo publish
doesn't do this.
The PulseAudio API description of pa_proplist_get
says:
The data pointer returned will point to an internally allocated buffer. The caller should make a copy of the data before the property list is accessed again.
However, the get function has a signature of pub fn get(&self, key: &str) -> Option<&[u8]>
, allowing safe Rust code to access the memory way after another access has happened.
The mismatch here is that get only takes a shared pointer, and thus limits the lifetime to as long as nothing "modifies" self, but the PA API already considers access a mutation.
I've found this tracking down why myxer doesn't display the name of an (admittedly broken, but hey it's a cheap Bluetooth gadget, and the name is untrusted data after all) amplifier. I didn't run into trouble with this precisely (the fault I was looking for is somewhere else), and chances are the names do regularly point into memory isn't really mutated by later accesses, but the PA API doesn't guarantee it, and may change at any time.
Options as I see them are:
get
and anything else that "accesses" the proplist take a &mut self.I don't see any easy way to do either of them without breaking the API. Worse, the latter (which may sound easier on API users) would require locking for all accesses because Proplist implements Sync, so another thread could perform a get at the same time and copying the data out just before get returns would not be enough.
Hello,
I'm using load_module
in libpulse-binding, which states in the docs
Returns None on error, i.e. invalid arguments or state. The callback is provided with the index.
But this never happens.
If you create a module-null-sink with invalid params IterateResult won't show you any errors, nor the Operation. And I don't see where any Option
is used, to return any kind of None.
The doxygen from pa sadly isn't very helpful either..
I would like to signal a threaded mainloop in the ctrl+c handler in order to make it exit as cleanly as possible.
The crate I am using for it leverages a thread to handle the command. I thought I could pass the mainloop using an Arc and a Mutex.
However, the Rc in the mainloop does not implement Sync and Send and seems to prevent passing the entire object.
Was it intended? If it is the case, is there another way to do that?
Hi! I'm a Rust beginner, so I might be missing something obvious. I want to subscribe to the server and get notified when the volume of any sink input changes. I've set up a callback using get_sink_input_info
and it's firing as intended, but the parameter of interest is of type SinkInputInfoInternal. How can I convert this to the proper Rust struct
type?
Up to and including version 2.12 of the binding crates and version 1.10 of the sys crates available in this repository, they have been licensed with LGPL v2.1+, with a possible alternative of dual MIT and Apache-2.0 licensing under certain circumstances (as previously discussed in the project readme).
As of v2.13 (bindings) and v1.11 (sys) respectively, I have changed the licensing, dropping LGPL and promoting dual MIT and Apache-2.0 to be the new licensing model.
LGPL is a variant of GPL intended for use with libraries; it relaxes the restriction that forces projects using GPL code to also be GPL licensed. The requirement of LGPL licensed code is that if it is compiled into a separate object, i.e. a dynamically-loaded library (like a system library - .dll/.so) then such a requirement does not apply. It thus only applies if compiled statically, i.e. combined directly into an application or library object, whereby that object must be GPL/LGPL licensed.
This meant that for complying with the license, to make use of these sys and binding crates, you would be forced to either license anything using them as GPL/LGPL, or to compile them into shared libraries that sit alongside your project, which is not really ideal for a thin binding layer.
The dual MIT and Apache-2.0 licensing imposes no such requirements.
Note that the default way Rust crates are consumed, through simply adding dependencies to a Cargo.toml file, is static compiling! All of your dependencies, unless you explicitly compile them as a shared library, are being compiled statically. There is thus risk that users may blindly depend on these crates and violate the licensing in doing so.
Furthermore, I, the project author, have absolutely no interest in imposing such a restriction on use of these crates.
PulseAudio itself is LGPL 2.1+ licensed, and I had to consider this when I created these crates. I was concerned about the possibility of these crates being considered derivative works of PulseAudio through for instance the fact that they largely copy the documentation from the PulseAudio C header files.
I thus simply went with licensing this project under the same LGPL 2.1+ licensing to avoid any potential for compliance issues.
Nothing has really changed, but I have re-evaluated the situation.
I have discussed the issue with the PulseAudio project (here). Their stance is that although the LGPL license makes certain common sense exceptions, there is no actual exception for copying documentation like I am doing. The only way to get their official approval would be to build a list of all individuals who have ever contributed to the PulseAudio documentation and get their explicit approval, which of course would be a pain.
My feeling is that perhaps the LGPL license authors had not considered a situation like this when they wrote it, and that what I am doing is not in the spirit of what their license was trying to
restrict. They have a contact address for asking such questions, and I emailed them requesting clarification on their position on this. I have not yet received a response.
Of interest of course is that documentation does not actually get compiled into any resulting library or application. It does however become part of documentation generated by cargo doc
.
Another factor of interest is that there is a FAQ available for the GPL/LGPL licenses (here), and specifically there is an entry on "fair-use" (here) which explicitly makes clear that fair-use does apply...
What I have decided is that the nature of my project and use of the PA documentation within it
should reasonably be considered fair-use, and that I should thus not feel bound to using LGPL for my crates.
My project is simply providing a free Rust interface to PA; I'm maintaining it myself independently
largely because they told me previously that it was too burdensome for them to merge into the
official project; and providing a copy of the documentation as I do is extremely valuable to users
of this Rust interface to PA. It would be very unreasonable for users to have to consult the C
header files. It is just a matter of making the documentation available in the location most useful
to Rust users, where they would expect to find it. That is the purpose in having copied it. Not
doing so would make use of PA for Rust programmers significantly unpleasant.
We also come back to that issue of combining LGPL code within compiled objects. This documentation, being documentation, is never compiled into libraries or applications (ignoring doc-tests, which is fair-use anyway as being examples), so there is actually the licensing restriction should not even apply to projects depending on these crates so long as they just depend upon them and not copy and paste stuff.
The only applicable "derivative-work" related use of the documentation where it gets combined into something, if this is even any concern of LGPL, is the HTML documentation generated by cargo doc
. Here I feel that personal generation of this HTML documentation is also surely a clear fair-use
situation.
Furthermore I feel that the copy of the HTML documentation hosted on docs.rs is again a fair-use situation - it is just providing a helpful free resource for Rust users.
I have thus decided to move forward with changing the licensing of these crates.
You can now freely depend upon the crates, allowing them to be compiled-in statically, as natural, without worry of being being in violation of the LGPL license. You should also of course be able to generate and make use of the HTML based documentation with cargo doc
without concern.
That is, as long as you are simply making normal use of these crates. If you're considering
doing something unusual like copying the documentation into something else like your own code or into a book you wish to publish, then you should evaluate whether my fair-use of that documentation reasonably would carry over to your use of it, or whether you must comply with the original LGPL license.
If, as suggested, I:
Add this to your Cargo.toml:
[dependencies] libpulse_binding = "2.0"
then this happens:
$ cargo build
Updating crates.io index
error: no matching package named `libpulse_binding` found
location searched: registry `https://github.com/rust-lang/crates.io-index`
did you mean: libpulse-binding
required by package `foo v0.1.0 (/home/josh/src/foo)`
Hi,
In libpulse_binding::mainloop::api::standard::Mainloop
(this line) and libpulse_binding::mainloop::api::threaded::Mainloop
(this line), there is the function get_api
which returns a borrow to the API vtable. I think the lifetime annotations of this function are incorrect and could lead to unexpected behavior.
pub fn get_api<'a>(&self) -> &'a ::mainloop::api::MainloopApi {
...
}
The returned borrow can live even after the Mainloop is freed and the Api is destroyed. As the function's docstring says, "No need to free the API as it is owned by the loop and is destroyed when the loop is freed."
I believe that something like this shouldn't be allowed to compile:
let mut mainloop = Mainloop::new().expect("Failed to create Mainoop");
let api: &MainloopApi = mainloop.get_api();
drop(mainloop); // Also frees API vtable
// Use api here
Changing the function signature to this would solve the issue and prevent the above code from compiling.
pub fn get_api(&self) -> & ::mainloop::api::MainloopApi
I was already considering once to write a pulseaudio binding, but more high-level. I still feel this binding exposes a lot of details of dealing with the threading model of pulseaudio. I was wondering if there would be a way to utilize futures, but I am not particularly proficient with those yet.
Especially the callback stuff still requires one to pass C pointers around: https://docs.rs/libpulse-binding/1.0.3/libpulse_binding/context/type.ContextNotifyCb.html
I'm aware that CI is failing after just having bumped the OS from ubuntu 20.04 to 22.04. I don't know why this is happening, so far I've failed to find a solution, and I'm rather frustrated, especially with docs.rs builds also failing. Help would be appreciated.
What is happening seems to be as follows - compiling works just fine, but when it comes to running tests, the doctests that try to run a basic example of using pulseaudio are failing to setup a pulseaudio context object, with the pulseaudio client library returning a -1
error, corresponding to an "access" error condition.
This isn't a code issue. The tests work fine locally, and worked just fine in the ubuntu 20.04 environment. Something is wrong with the new OS environment itself. 22.04 has pulseaudio version 15.99 rather than 13.99, but I don't expect that's somehow the issue.
Researching pulseaudio issues on 22.04 I've come across other people having issues with pulseaudio on their own machines after upgrading from 20.04, and I've tried some of the suggestions from here, but with no luck. In fact it seems that the CI ubuntu 22.04 environment does not even have pipewire installed. Installing pipewire (pipewire
, pipewire-pulse
, and pipewire-media-session
) and then doing what's suggested in that thread did not help.
What on earth have the ubuntu guys done to break PA?
Can I get this fixed or must I go back to 20.04, which obviously can only be a temporary measure, not a permanent future-proof fix? :/
The types listed in the title all share the issue that when you receive them in callbacks passed to methods like get_sink_info_by_name
, you can't use those types outside of the callbacks due to lifetime constraints.
I first tried to address the issue by deriving Clone
for the relevant structs, but I realized that won't solve the problem since cloning a Cow::Borrowed
just produces a copy of the reference it contains. I next tried to see if the derive-into-owned crate would work, but the derive seems to fail on some types, for example SinkInfo
.
I'm not sure whether it's safe to make Context send, but I'm currently failing to share an Arc<Mutex<Context>>
due to
`*const extern "C" fn(*mut pulse_sys::pa_context, *const i8, *mut pulse_sys::pa_proplist, *mut std::ffi::c_void)` cannot be sent between threads safely
I'm not sure if it helps, this is the full log output when trying to do:
(move || ) the context Arc<Mutex<_>>
help: within `pulse::context::Context`, the trait `std::marker::Send` is not implemented for `*const extern "C" fn(*mut pulse_sys::pa_context, *const i8, *mut pulse_sys::pa_proplist, *mut std::ffi::c_void)`
= note: required because it appears within the type `std::marker::PhantomData<*const extern "C" fn(*mut pulse_sys::pa_context, *const i8, *mut pulse_sys::pa_proplist, *mut std::ffi::c_void)>`
= note: required because it appears within the type `pulse::callbacks::MultiUseCallback<(dyn std::ops::FnMut(std::string::String, pulse::proplist::Proplist) + 'static), extern "C" fn(*mut pulse_sys::pa_context, *const i8, *mut pulse_sys::pa_proplist, *mut std::ffi::c_void)>`
= note: required because it appears within the type `pulse::context::CallbackPointers`
= note: required because it appears within the type `pulse::context::Context`
= note: required because of the requirements on the impl of `std::marker::Send` for `std::sync::Mutex<pulse::context::Context>`
= note: required because of the requirements on the impl of `std::marker::Send` for `std::sync::Arc<std::sync::Mutex<pulse::context::Context>>`
I'm trying to share a Context & Mainloop with a hyper thread, both wrapped inside an Arc<Mutex<_>>, so my only option for now would be to handle these in a new thread and use an mpsc channel to communicate between them.
When using mainloop::threaded::Mainloop
, the callbacks that one gives to functions like context::introspect::Introspector::get_server_info
are executed on a different thread. I think that means that those callbacks must be required to be Send
, just like a callback for std::thread::spawn
is required to be Send
.
The build error stems from libpulse_sys::version::actual
module being defined twice, presumably either because of incorrectly written version feature guards or a mistake in a Cargo.toml
somewhere.
Link to the build error: https://docs.rs/crate/libpulse-binding/2.24.0/builds/416491
Now, I have no idea if this is how the API was intended to be used, but as I was figuring out how to upload samples I discovered this issue.
First, I was using @danieldg's libpulse-tokio
main loop implementation, but then I created a small repository which implemented creating a stream and uploading it as a sample with both libpulse-tokio
as well as the standard threaded main loop implementations, and this is definitely an issue with both.
The repository I created is here: https://github.com/acheronfail/pulse-stream-segfault/.
When calling stream.set_write_callback(None)
inside a write callback, a segfault occurs.
I expected nothing to happen. In fact, if you call stream.set_state_callback(None)
inside the state callback, then no error occurs. This is why I expected nothing to happen.
This is basically the code:
let stream = Rc::new(RefCell::new({/* code to create a stream object */}));
let stream_ref = stream.clone();
stream.borrow_mut().set_write_callback(Some(Box::new(move |len| {
// do the actual write...
// SEGFAULT HERE:
stream_ref.borrow_mut().set_write_callback(None);
})));
Have a look at the repository I created here:
libpulse_binding
?libpulse
API incorrectly - does it not support this operation?The documentation of Proplist::get_str says:
Will return None if the key does not exist or if data is not valid UTF-8.
However, the implementation performs .to_string_lossy()
, returns Some(String) where the string just contains replacement characters.
I think the implementation behavior makes more sense than the documented one (If my soundcard is named b"Xpecial Audio \xff", I prefer to see "Xpecial Audio �" over "Unnamed card" or whatever the application makes of a None, just the documentation doesn't say that.
https://github.com/jnqnfe/pulse-binding-rust/blob/master/pulse-binding/src/mainloop/standard.rs#L224
Technically fixing this would be a breaking change and it can be trivially worked around with InterateResult as IterateResult
, but I would personally appreciate it.
Hey, thanks for creating this higher-level abstraction for pulse
!
How would you recommend managing mainloop
when I'm dealing with callbacks?
For example:
let mainloop: Rc<RefCell<Mainloop>> = ...
let context = Rc<RefCell<Context>> = ...
let op = {
let mainloop = mainloop.clone();
context.borrow().introspect().get_server_info(move |info| {
let op = context.borrow().introspect().get_sink_info_by_name(&info.default_sink_name.unwrap(), move |_| {
println!("neat");
});
# loop
# mainloop.iterate
# break if op.get_state() == Done
#
})
}
# loop
# mainloop.iterate => at this point, callback is called
# break if op.get_state() == Done
#
I'm running into an issue where mainloop
is in use when my callback is called. So, when I need to wait for my nested operation (get_sink_info_by_name(...)
), I can't borrow mainloop
to iterate, since it's already in use.
How would you recommend handling this?
I need Send
+Sync
for libpulse_simple_binding::Simple
for pretty much the same reason at #14.
I didn't look into it and don't know if it's actually safe, however is the ususal cases it is...
I am aware that recently that travis
has been consistently reporting failure recently. Do not fret, there is nothing wrong with the crates!
The problem is simply some issue with the travis setup itself, relating it seems to the version of PA used within the test environment.
I'm working on it.
The simple fix to gets things working again it appeared to be to disable PA v12 features in the testing, since no travis environment yet has PA v12. Note that previously we only had success with that due to a mistake that I have now corrected in regards to use of feature flags with our Cargo workspace, the fix for which has helped highlight issues with our use of travis. However, having now tried disabling PA v12 support, it is still broken, and as of this minute, I'm still trying to understand the cause.
Currently, three specific tests are failing, with no immediately obvious reason. The PA v12 symbol being missing in the system lib (too old a version) is mentioned, though I don;t see why, and may be a read herring. I really don't know. Investigation continues...
Discussion ongoing here: https://travis-ci.community/t/failure-due-to-system-libs-now-being-older/4712
This is a mirror of the TODO in pulse-sys-mainloop-glib
.
I'd like to be able to use MainContext
from the glib
crate as a parameter for Mainloop::new(...)
in pulse-binding-glib
.
I'd be interested in drawing up a PR for this, but I'm not sure how to peel the GMainContext
out of the glib
crate. MainContext
is defined as:
pub struct MainContext(Shared<glib_sys::GMainContext>);
I'm not sure how to get at the Shared
structure, since it's private. Once that's accomplished, though, I'm assuming I can just shared.borrow_mut()
to get at the GMainContext
inside?
say i have a card, and i need to change the audio profile of that card, so i use get_card_info_by_name
to get the info of that card to decide what profile to choose. however this returns the info in a ListResult<&CardInfo>'_>>
, and it doesn't seem i can iterate over it or unwrap it to get the underlying CardInfo
or anything, how would i get info about a card this way?
Functions like https://docs.rs/libpulse-binding/2.26.0/libpulse_binding/context/introspect/struct.Introspector.html#method.get_server_info take FnMut
. The function shouldn't be called more than once, right? Then FnOnce
is more general and appropriate.
I guess this would apply to a lot of callbacks...
For the record, I have no experience with the PulseAudio C library, so this very well could be me just overlooking something.
The entirety of my code is:
[main.rs]
use anyhow::Result;
use wizja::pulse::PARecorder;
fn main() -> Result<()> {
let args: Vec<String> = env::args().collect();
let mut rec = PARecorder::new()?;
println!("{:#?}", rec.buf);
Ok(())
}
[pulse.rs]
extern crate libpulse_binding as pulse;
extern crate libpulse_simple_binding as psimple;
use anyhow::{anyhow, Result};
use psimple::Simple;
use pulse::sample;
use pulse::stream::Direction;
const SPEC: sample::Spec = sample::Spec {
format: sample::SAMPLE_S16NE,
channels: 2,
rate: 44100,
};
pub struct PARecorder {
srv: Simple,
pub buf: Vec<u8>,
}
impl PARecorder {
pub fn new() -> Result<PARecorder> {
assert!(SPEC.is_valid());
let srv = Simple::new(
None,
"wizja",
Direction::Record,
None,
"Visualizer",
&SPEC,
None,
None,
)
.map_err(|e| match e.to_string() {
Some(s) => anyhow!(s),
None => anyhow!("???"),
})?;
Ok(PARecorder {
srv,
buf: Vec::with_capacity(10000),
})
}
pub fn update(&mut self) -> Result<()> {
self.srv
.read(&mut self.buf)
.map_err(|e| match e.to_string() {
Some(s) => anyhow!(s),
None => anyhow!("???"),
})?;
Ok(())
}
}
When running this, just to test if I get any microphone input, I get Error: Invalid argument
(which I think is PAError(3)
). What's wrong with this code? It seems similar enough to the parec-simple.c example from Pulse's Doxygen (which does run on my system).
Hi, I try to cross compile my project that's depends on libpulse-binding and libpulse-simple-binding from archlinux (x86_64) targeting aarch64-unknown-linux-gnu, but I got this error.
cargo build --target aarch64-unknown-linux-gnu
Compiling libpulse-sys v1.20.1
The following warnings were emitted during compilation:
warning: pkg-config has not been configured to support cross-compilation.
error: failed to run custom build command for `libpulse-sys v1.20.1`
Caused by:
process didn't exit successfully: `/home/admur/Dev/rpulse/target/debug/build/libpulse-sys-d2947d344d65e7ef/build-script-build` (exit status: 1)
--- stdout
cargo:rerun-if-env-changed=LIBPULSE_NO_PKG_CONFIG
cargo:rerun-if-env-changed=PKG_CONFIG_ALLOW_CROSS_aarch64-unknown-linux-gnu
cargo:rerun-if-env-changed=PKG_CONFIG_ALLOW_CROSS_aarch64_unknown_linux_gnu
cargo:rerun-if-env-changed=TARGET_PKG_CONFIG_ALLOW_CROSS
cargo:rerun-if-env-changed=PKG_CONFIG_ALLOW_CROSS
cargo:rerun-if-env-changed=PKG_CONFIG_aarch64-unknown-linux-gnu
cargo:rerun-if-env-changed=PKG_CONFIG_aarch64_unknown_linux_gnu
cargo:rerun-if-env-changed=TARGET_PKG_CONFIG
cargo:rerun-if-env-changed=PKG_CONFIG
cargo:rerun-if-env-changed=PKG_CONFIG_SYSROOT_DIR_aarch64-unknown-linux-gnu
cargo:rerun-if-env-changed=PKG_CONFIG_SYSROOT_DIR_aarch64_unknown_linux_gnu
cargo:rerun-if-env-changed=TARGET_PKG_CONFIG_SYSROOT_DIR
cargo:rerun-if-env-changed=PKG_CONFIG_SYSROOT_DIR
cargo:rerun-if-env-changed=LIBPULSE_NO_PKG_CONFIG
cargo:rerun-if-env-changed=PKG_CONFIG_ALLOW_CROSS_aarch64-unknown-linux-gnu
cargo:rerun-if-env-changed=PKG_CONFIG_ALLOW_CROSS_aarch64_unknown_linux_gnu
cargo:rerun-if-env-changed=TARGET_PKG_CONFIG_ALLOW_CROSS
cargo:rerun-if-env-changed=PKG_CONFIG_ALLOW_CROSS
cargo:rerun-if-env-changed=PKG_CONFIG_aarch64-unknown-linux-gnu
cargo:rerun-if-env-changed=PKG_CONFIG_aarch64_unknown_linux_gnu
cargo:rerun-if-env-changed=TARGET_PKG_CONFIG
cargo:rerun-if-env-changed=PKG_CONFIG
cargo:rerun-if-env-changed=PKG_CONFIG_SYSROOT_DIR_aarch64-unknown-linux-gnu
cargo:rerun-if-env-changed=PKG_CONFIG_SYSROOT_DIR_aarch64_unknown_linux_gnu
cargo:rerun-if-env-changed=TARGET_PKG_CONFIG_SYSROOT_DIR
cargo:rerun-if-env-changed=PKG_CONFIG_SYSROOT_DIR
cargo:warning=pkg-config has not been configured to support cross-compilation.
Install a sysroot for the target platform and configure it via
PKG_CONFIG_SYSROOT_DIR and PKG_CONFIG_PATH, or install a
cross-compiling wrapper for pkg-config and set it via
PKG_CONFIG environment variable.
Sometimes, when running a program I'm developing with libpulse-binding, I get the following error in the terminal:
Assertion 'q->front' failed at pulsecore/queue.c:81, function pa_queue_push(). Aborting.
Aborted (core dumped)
Unfortunately, I can't seem to get any more information, since no stack traces are given, even when the flag is enabled.
A quick google search results in this issue, which suggests that the bug may have been fixed in a newer version of PulseAudio.
I was wondering if that's something that would need to be changed on your end, or on mine, and if so, what would have to be done? It seems that on other devices, this error will sometimes pop up randomly during the runtime of the application.
Hi,
First, thank for your amazing work!
I have no previous experience with the PulseAudio C library and I'm just trying to use your binding to retrieve the informations of the sink (volume, muted).
I started by using the standard loop (I have no clue if it's a good idea or not, for my simple needs).
What I have done successfully is to create a context and connect it to the pulseaudio running server.
Also I can subscribe to sink events and get the callback fired as expected. Now the problem is I fail to retrieve the sinks info. Either from the subscribe callback or from the context's introspection. In the doc there is no example for that (and I find it pretty complex for a beginner without some previous experience with PulseAudio).
Also the relationship between the main loop and the context introspection/subscribed events is pretty vague.
Can you help me ?
EDIT: After a couple more hours of diving in the doc and the code, I finally got it. So all my question above are not relevant anymore.
Here is my simple code:
use libpulse_binding as pulse;
use pulse::callbacks::ListResult;
use pulse::context::subscribe::subscription_masks;
use pulse::context::{flags, Context};
use pulse::def::Retval;
use pulse::mainloop::standard::IterateResult;
use pulse::mainloop::standard::Mainloop;
use pulse::proplist::Proplist;
use std::cell::RefCell;
use std::ops::Deref;
use std::rc::Rc;
pub fn run() {
let mut proplist = Proplist::new().unwrap();
proplist
.set_str(pulse::proplist::properties::APPLICATION_NAME, "FooApp")
.unwrap();
let mainloop = Rc::new(RefCell::new(
Mainloop::new().expect("Failed to create mainloop"),
));
let mut context =
Context::new_with_proplist(mainloop.borrow().deref(), "FooAppContext", &proplist)
.expect("Failed to create new context");
context.connect(None, flags::NOFAIL, None).unwrap();
// Wait for context to be ready
loop {
match mainloop.borrow_mut().iterate(false) {
IterateResult::Quit(_) | IterateResult::Err(_) => {
eprintln!("Iterate state was not success, quitting...");
return;
}
IterateResult::Success(_) => {}
}
println!("{:#?}", context.get_state());
match context.get_state() {
pulse::context::State::Ready => {
break;
}
pulse::context::State::Failed | pulse::context::State::Terminated => {
eprintln!("Context state failed/terminated, quitting...");
return;
}
_ => {}
}
}
let interest = subscription_masks::SINK;
let mut subscription = context.subscribe(interest, |_| {});
context.set_subscribe_callback(Some(Box::new(|facility_opt, operation_opt, index| {
// here the callback is fired when I mute or change the volume, but how can I get the sink
// info from inside the clojure ?
println!("{:#?}\n{:#?}\n{:#?}\n", facility_opt, operation_opt, index);
})));
// Our main loop
loop {
// I don't know if this is useful or not ?
match mainloop.borrow_mut().iterate(false) {
IterateResult::Quit(_) | IterateResult::Err(_) => {
eprintln!("Iterate state was not success, quitting...");
return;
}
IterateResult::Success(_) => {}
}
let introspector = &context.introspect();
introspector.get_sink_info_list(|i| println!("")); // I don't know what to do with that. How can I get back the result ?
}
// Clean shutdown
mainloop.borrow_mut().quit(Retval(0));
}
When trying to delete a failed stream (from a HashMap
) I get this error:
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: PAErr(-15)', libcore/result.rs:1009:5
stack backtrace:
0: std::sys::unix::backtrace::tracing::imp::unwind_backtrace
1: std::sys_common::backtrace::_print
2: std::panicking::default_hook::{{closure}}
3: std::panicking::default_hook
4: std::panicking::rust_panic_with_hook
5: std::panicking::continue_panic_fmt
6: rust_begin_unwind
7: core::panicking::panic_fmt
8: core::result::unwrap_failed
at /build/rust/src/rustc-1.30.1-src/src/libcore/macros.rs:26
9: <core::result::Result<T, E>>::unwrap
at /build/rust/src/rustc-1.30.1-src/src/libcore/result.rs:808
10: <libpulse_binding::stream::Stream as core::ops::drop::Drop>::drop
at /home/futpib/.cargo/registry/src/github.com-1ecc6299db9ec823/libpulse-binding-2.3.0/src/stream.rs:1613
11: core::ptr::drop_in_place
at /build/rust/src/rustc-1.30.1-src/src/libcore/ptr.rs:59
12: core::ptr::drop_in_place
at /build/rust/src/rustc-1.30.1-src/src/libcore/ptr.rs:59
13: core::ptr::drop_in_place
at /build/rust/src/rustc-1.30.1-src/src/libcore/ptr.rs:59
14: core::ptr::drop_in_place
at /build/rust/src/rustc-1.30.1-src/src/libcore/ptr.rs:59
15: <alloc::rc::Rc<T> as core::ops::drop::Drop>::drop
at /build/rust/src/rustc-1.30.1-src/src/liballoc/rc.rs:835
16: core::ptr::drop_in_place
at /build/rust/src/rustc-1.30.1-src/src/libcore/ptr.rs:59
17: <std::collections::hash::map::HashMap<K, V, S>>::retain
at /build/rust/src/rustc-1.30.1-src/src/libstd/collections/hash/map.rs:1471
18: papeaks::main
at src/main.rs:181
19: std::rt::lang_start::{{closure}}
at /build/rust/src/rustc-1.30.1-src/src/libstd/rt.rs:74
20: std::panicking::try::do_call
21: __rust_maybe_catch_panic
22: std::rt::lang_start_internal
23: std::rt::lang_start
at /build/rust/src/rustc-1.30.1-src/src/libstd/rt.rs:74
24: main
25: __libc_start_main
26: _start
I think Drop
implementation should not call disconnect
if the stream is not in PA_CONTEXT_READY
as PulseAudio's pa_stream_disconnect
returns PA_ERR_BADSTATE
which looks like the reason behind PAErr(-15)
.
pulse-binding-rust/pulse-binding/src/stream.rs
Lines 1611 to 1617 in 012e15c
pulse-binding-rust/pulse-binding/src/stream.rs
Lines 864 to 869 in 012e15c
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.