jnqnfe / pulse-binding-rust

FFI and bindings for using PulseAudio from the Rust programming language.
Apache License 2.0
67 stars 20 forks source link

Windows support #28

Closed allquixotic closed 4 years ago

allquixotic commented 4 years ago

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.

jnqnfe commented 4 years ago

Hi. I haven't had a Windows installation for quite some time now, so unable to check directly unless I can manage to get a VM setup for this, I'll have to see. I'll look into it. I don;t have an immediate answer for you unfortunately, though do note that Cargo supports "patching" crates with custom fixes so you can use that to push ahead with your work without having to wait on me if you do figure out a fix. :)

edit: actually, so if pollfd is missing on Windows in libc, you can either use the patch mechanism just mentioned to hack it into libc on Windows, or hack the libpulse-sys file using the libc type to include the definition there perhaps... I'll look into a proper fix later...

jnqnfe commented 4 years ago

Okay, update. I've just published v1.11.1 of libpulse-sys which fixes the pollfd definition (I would presume - awaiting your confirmation since I don't have Windows).

I remember now considering this very issue of pollfd not being defined in libc for Windows in the early stages of building this crate, and wondering what definition we were being expected to use for Windows instead but I never asked anyone. Since I wasn't sure if there even were any Windows PulseAudio users out there, I did not give much thought to it again, assuming that maybe libc would add it later. It seems they have not.

What I have done is to link to the WSAPOLLFD definition in the winapi crate (for Windows, sticking otherwise with pollfd from libc), with winapi seeming to be a popular crate for Windows API use.

As I said, I don't expect we have many Windows PulseAudio users, so you may well be the first. Since I have not been in a position to check things on Windows thus far, there may be some teething issues such as this to be experienced. I hope this was the only one and the new version fixes things. My oppologies for the difficulties.

Of course it might be ideal if our Travis setup tested Windows, which would both have caught this issue earlier and give future confidence going forwards that such issues do not reoccur, however that would entail figuring out how to get PA installed on Windows to setup the environment correctly first, and I'm not certain overall if all the effort in even running automated tests on Windows for every change I make is really worth the resource costs (let's be considerate of the environment at least?).

allquixotic commented 4 years ago

Thanks for the updates and for the work on this binding! This is really helpful. I actually broke down and wrote a bunch of code to avoid using pulseaudio on Windows altogether for my immediate use case, but I can still think of some valid uses of PulseAudio on Windows (virtualization with high-speed TCP/IP between guest and host comes to mind).

It's a bit off-topic, but my high level "problem statement" that led me to pulse-binding-rust was that I wanted to listen to music through Spotify on Windows with a modified (shifted) pitch. The solution wasn't at all obvious, but I ended up finishing a gstreamer backend for librespot (a Rust program that can play Spotify songs) that the original author started in 2016 but didn't ever finish. It needed to be updated to use the latest gstreamer bindings for Rust and a few other tweaks, but it was 90% there and the overall design was sound. Once I wrote the code, I was able to compile it cleanly in a MinGW environment that had gstreamer-1.0 (and plugins) installed, and I don't need PulseAudio because my gstreamer pipeline can push the samples straight to Windows DirectSound. https://github.com/librespot-org/librespot/compare/dev...allquixotic:gst1.0-2020

Earlier iterations of my design involved using the pre-existing pulseaudio backend for librespot, a pulseaudio daemon, and a separate gstreamer pipeline using the pulseaudio monitor source. The first iteration had everything except for the pulseaudio daemon running on Linux in WSL2 (basically a virtual machine), then a second iteration had everything running on native Windows (pulseaudio daemon, librespot, and a gstreamer pipeline). The third iteration (my patches above) is infinitely simpler and has less latency.

Anyway, I want to help improve these bindings by providing testing feedback, so I'll continue...

The native code PA daemon and client lib do build successfully on Windows, but with the latest stable they seem to require a small patchset to build. Oddly, the patchset is slightly different depending on which toolchain you use. I had a few more problems with compiling PA on MinGW on Windows itself than I did using Fedora 31's MinGW cross compiler toolchain.

For what it's worth, if you want to do cross compilation from Linux to Windows, Fedora's toolchain is probably the best; Debian's is not as well maintained and doesn't have an easy mechanism to set all the required environment variables for cross compilation in GNU autotools. On Fedora, mingw64-configure is a truly remarkable script; it queries rpm for a series of environment variables to set and then invokes ./configure in a way that it pretty much exactly recognizes your cross build environment with no "gotchas." They also have mingw64-make and mingw64-cmake for analogous purposes.

Here are some good patches for building PulseAudio (client and daemon) for Windows using any MinGW toolchain, either cross compile or directly: https://github.com/Warblefly/MultimediaTools-mingw-w64/blob/master/pulseaudio-conf.patch and https://github.com/Warblefly/MultimediaTools-mingw-w64/blob/master/pulseaudio-size.patch

At the moment, I'm unable to easily test your changes by actually running them because my code depends on libpulse-simple-binding 2.14.0, which depends on libpulse-sys 1.11.

However, I added an explicit dependency to my main Cargo.toml for libpulse-sys 1.11.1, and it compiled successfully using MSYS2 MinGW64 (a native Windows GNU-alike toolchain). After libpulse-sys 1.11.1 successfully compiled, I found another instance of libc::pollfd within libpulse-simple-binding 2.14.0, as well as some other odds and ends that don't compile on Windows:

error[E0432]: unresolved import `libc::pollfd`
   --> C:\Users\smcna\.cargo\registry\src\github.com-1ecc6299db9ec823\libpulse-binding-2.14.0\src\mainloop\standard.rs:207:5
    |
207 | use libc::pollfd;
    |     ^^^^^^^^^^^^ no `pollfd` in the root

error[E0412]: cannot find type `suseconds_t` in crate `libc`
  --> C:\Users\smcna\.cargo\registry\src\github.com-1ecc6299db9ec823\libpulse-binding-2.14.0\src\time\timeval.rs:62:53
   |
62 |     pub const fn new(sec: libc::time_t, usec: libc::suseconds_t) -> Self {
   |                                                     ^^^^^^^^^^^ not found in `libc`

error[E0412]: cannot find type `suseconds_t` in crate `libc`
   --> C:\Users\smcna\.cargo\registry\src\github.com-1ecc6299db9ec823\libpulse-binding-2.14.0\src\time\timeval.rs:100:69
    |
100 |             true => { self.0.tv_usec |= PA_TIMEVAL_RTCLOCK as libc::suseconds_t; },
    |                                                                     ^^^^^^^^^^^ not found in `libc`

error[E0412]: cannot find type `suseconds_t` in crate `libc`
  --> C:\Users\smcna\.cargo\registry\src\github.com-1ecc6299db9ec823\libpulse-binding-2.14.0\src\time\mod.rs:54:59
   |
54 |         Timeval::new(secs as libc::time_t, usecs as libc::suseconds_t)
   |                                                           ^^^^^^^^^^^ not found in `libc`

error[E0412]: cannot find type `suseconds_t` in crate `libc`
  --> C:\Users\smcna\.cargo\registry\src\github.com-1ecc6299db9ec823\libpulse-binding-2.14.0\src\time\mod.rs:74:78
   |
74 |         Timeval::new(t.as_secs() as libc::time_t, t.subsec_millis() as libc::suseconds_t)
   |                                                                              ^^^^^^^^^^^ not found in `libc`

   Compiling pbkdf2 v0.3.0
   Compiling aes-ctr v0.3.0
   Compiling backtrace v0.3.33
   Compiling env_logger v0.6.2
error[E0308]: mismatched types
  --> C:\Users\smcna\.cargo\registry\src\github.com-1ecc6299db9ec823\libpulse-binding-2.14.0\src\time\timeval.rs:63:41
   |
63 |         Timeval(libc::timeval { tv_sec: sec, tv_usec: usec })
   |                                         ^^^ expected i32, found i64

So as of right now, to summarize:

jnqnfe commented 4 years ago

thanks for the update

firstly, note that you have to run cargo update for it to pick up the existence of updated crates and update your project to use them, which is probably why yours did not pick up the 1.11.1 update of libpulse-sys. note that this update mechanism will only update the project like this for crate updates that fall within the versioning constraints specified in your dependencies, so for instance if a new minor version of a crate is published then cargo update will not see it unless you've set the dependency version constraint to allow it to.

I checked the other libc components used at the sys level before publishing the update. I don;t know why I did not also check the binding layer. thanks for pointing out the issues. I've fixed the pollfd issue in the binding locally. the suseconds_t one will be slightly trickier. I'll tackle that shortly and publish a new version once I have.

jnqnfe commented 4 years ago

ok, so v2.14.1 of the binding crate now fixes the issues with pollfd and suseconds_t

additionally I have published v2.15 of the bindings and v1.12 of the *-sys layer to expand use of pkg-config for checking and linking to the PA libs to all platforms now rather than just Linux (others applied a simple fallback that I'm not certain even worked).

the remaining concern is simply one of lack of certainty whether the linking works correctly, most particularly when pkg-config is not used where the just mentioned alternative is then used as a fallback. I've published a further v1.12.1 of the *-sys packages to hopefully help the non-pkg-config fallback to work on Windows and MacOS cases.

allquixotic commented 4 years ago

Thanks, great work! I grabbed the latest versions; here's my update:

Not sure when I will get to the runtime testing but I'll keep it in mind. Thanks again!

jnqnfe commented 4 years ago

Great, thanks for your help :)