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

question, I'm stuck to just retrieve the sink infos #31

Closed doums closed 4 years ago

doums commented 4 years ago

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));
}
jnqnfe commented 4 years ago

First, thank for your amazing work!

No problem!

Sorry it took a couple days to get back to you; busy, busy.

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 ?

Yeah, interacting with PulseAudio is not the easiest thing to get to grips with. Most of the documentation here derives directly from that in the C interface and I'm sure that there might be undesirable deficiencies in explaining how to do things as you've experienced. If I had the time I'd try to take a further look at that but I'm very busy. I happened to be completely new to PulseAudio myself when I started this project and it took a bit of experimenting to get to grips with things sufficiently such that I could have a working threaded mainloop example to test stuff with since there was just no complete example of how on earth to do it. I did update the documentation with such an example to cover that aspect at least.

Do feel free though to create bug reports for areas where the documentation doesn't explain things well or give sufficient examples. It's a very valid area of improvement worth reporting, and although I will try to consider improvements myself if I can find time, it can be very helpful to know of specific issues that users had trouble with. I can't promise to actually get on top of such reports quickly, but they can sit there as a reminder for a little while, and someone else might very well come along, notice, and decide to offer up a patch (which you're welcome to do yourself), and a patch might also take less time to review than doing it myself.

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.

Okay, great! :)

I'll skip looking at the code you posted then if you don't mind; too much else to do to spend time looking at it if it's not needed.

Edit: Oh, note that the actual PulseAudio project has a mailing list. I don't know whether anyone actively on it actually has experience with my Rust binding and thus could give specific advice for using it, but the interface this binding provides is not very far removed from the C interface underneath it and so their mailing list offers another good resource in terms of at least being able to get help and advice on concepts with the C interface which could roughly translate to use with this binding.

jnqnfe commented 3 years ago

[Responding to something that was deleted before I had submitted my response]

Hi,

No problem. From a quick look, your problem seems to be because you're not using the mainloop's iterate() function within that last loop, thus you never let the PA mainloop process the messages sent to it from the PA server to receive the requested data and update the state of sources to 'done'.

Take a look at the loop you copied with the "Wait for context to be ready" comment; notice there that the mainloop's iterate() function is being called in each loop (and checked for error) before trying to read the context state. If you check the example in the documentation for the standard mainloop you'll also notice that this is done in multiple places. You simply need to duplicate that bit at the start of your loop.

Regards, Lyndon

On Sat, 2021-01-16 at 07:48 -0800, Ken wrote:

Hi :) Sorry for bringing back a closed issue, i actually have a similar problem, i can't really understand how to use data from the callbacks, especially for example with Introspector::get_source_info_list, this issue seems to be similar, but @doums didnt post the solution to his problem 😭. This is the example, most of the code comes from the example from the documentation, except the end where i try to list source devices. let spec = sample::Spec { format: sample::Format::S16le, channels: 2, rate: 48000, }; assert!(spec.is_valid());

let mut proplist = Proplist::new().unwrap(); proplist .set_str( libpulse_binding::proplist::properties::APPLICATION_NAME, "FooApp", ) .unwrap();

let mut mainloop = Rc::new(RefCell::new( Mainloop::new().expect("Failed to create mainloop"), ));

let mut context = Rc::new(RefCell::new( Context::new_with_proplist(mainloop.borrow().deref(), "FooAppContext", &proplist) .expect("Failed to create new context"), ));

context .borrow_mut() .connect(None, libpulse_binding::context::flags::NOFLAGS, None) .expect("Failed to connect context");

// Wait for context to be ready loop { match mainloop.borrowmut().iterate(false) { IterateResult::Quit() | IterateResult::Err() => { eprintln!("Iterate state was not success, quitting..."); panic!(); //return } IterateResult::Success() => {} } match context.borrow().get_state() { libpulse_binding::context::State::Ready => { break; } libpulse_binding::context::State::Failed | libpulsebinding::context::State::Terminated => { eprintln!("Context state failed/terminated, quitting..."); panic!(); //return } => {} } }

let introspector = Context::introspect(&*context.borrow()); let sources = Introspector::get_source_infolist(&introspector, || () ); //What do put in this callback ?

loop { match sources.get_state() { libpulse_binding::operation::State::Done => { break; } e => eprintln!("WTF ? {:?}", e), // This is always firing, so the callback never finishes ?? } } Thanks for you time; Cheers, — You are receiving this because you commented. Reply to this email directly, view it on GitHub, or unsubscribe.

jnqnfe commented 3 years ago

I just sent you a response covering the main issue - lack of iterating the mainloop in your loop - but then upon taking a last look at your message before closing it I noticed that I'd overlooked aspects of the comments you left, so there's some more that I should say...

Firstly, your last comment ("This is always firing, so the callback never finishes") demonstrates a misunderstanding connected to the lack of mainloop iteration just mentioned. Perhaps pointing out the iteration issue is sufficient, but perhaps not, so I'll just add that it is not the case that your callback "never finishes", in fact your callback never executes at all. As explained in the previous response, by not using the mainloop's iterate() function within your loop, you never give the mainloop a chance to process messages from the PA server, to thus receive the requested data, run the callback and update the operation state.

You also asked "What do put in this callback". First of all you need to understand the concept of what happens with "list" type callbacks - your callback takes a ListResult parameter, and it will be executed once per item in the list, and then a final extra time to signal the end of the list having been reached. So the first thing that your callback needs to do is check which ListResult variant the parameter is, such that it can act appropriately. Once you've added this you could then add a simple debug-print statement for items such that you can see a copy of the data your callback is being given.

What to do next, to properly make use of the data, rather depends upon what you want to do with the data. If simply printing some properties is all you want then that's simple because you can probably just do that directly within the callback. If you want to display it in a GUI, you could make the relevant GUI object available within the callback. If you want to move/copy some or all of the data out of the callback, then this is of course possible but not especially easy; you'll need to make use of Rc and RefCell around some type to hold the data in, create a clone of that Rc wrapped object, 'move' one instance into the closure and use it within the closure to capture the data, and then use the other instance outside of the closure to subsequently make use of the data. Note that unfortunately no Clone implementation exists (currently) for types like SourceInfo and you're only getting a reference to one, so you cannot just move or copy the entire object out, you'll have to copy the specific attributes you're interested in (I may look at implementing Clone soon, I'll add it to the to-do list), and also that attributes like strings only live as long as the execution of the callback, thus owned copies would need to be made if moving/copying those.

Regards, Lyndon

On Sat, 2021-01-16 at 07:48 -0800, Ken wrote:

Hi :) Sorry for bringing back a closed issue, i actually have a similar problem, i can't really understand how to use data from the callbacks, especially for example with Introspector::get_source_info_list, this issue seems to be similar, but @doums didnt post the solution to his problem 😭. This is the example, most of the code comes from the example from the documentation, except the end where i try to list source devices. let spec = sample::Spec { format: sample::Format::S16le, channels: 2, rate: 48000, }; assert!(spec.is_valid());

let mut proplist = Proplist::new().unwrap(); proplist .set_str( libpulse_binding::proplist::properties::APPLICATION_NAME, "FooApp", ) .unwrap();

let mut mainloop = Rc::new(RefCell::new( Mainloop::new().expect("Failed to create mainloop"), ));

let mut context = Rc::new(RefCell::new( Context::new_with_proplist(mainloop.borrow().deref(), "FooAppContext", &proplist) .expect("Failed to create new context"), ));

context .borrow_mut() .connect(None, libpulse_binding::context::flags::NOFLAGS, None) .expect("Failed to connect context");

// Wait for context to be ready loop { match mainloop.borrowmut().iterate(false) { IterateResult::Quit() | IterateResult::Err() => { eprintln!("Iterate state was not success, quitting..."); panic!(); //return } IterateResult::Success() => {} } match context.borrow().get_state() { libpulse_binding::context::State::Ready => { break; } libpulse_binding::context::State::Failed | libpulsebinding::context::State::Terminated => { eprintln!("Context state failed/terminated, quitting..."); panic!(); //return } => {} } }

let introspector = Context::introspect(&*context.borrow()); let sources = Introspector::get_source_infolist(&introspector, || () ); //What do put in this callback ?

loop { match sources.get_state() { libpulse_binding::operation::State::Done => { break; } e => eprintln!("WTF ? {:?}", e), // This is always firing, so the callback never finishes ?? } } Thanks for you time; Cheers, — You are receiving this because you commented. Reply to this email directly, view it on GitHub, or unsubscribe.

KenN7 commented 3 years ago

Hi :) Thnks a lot for your answer, in the meantime i had delete the comment because i figured out what you explained about the mainloop. I'm sorry to have bothered you, i'm gratefull that your answered, i understand the problem much better now :) Thanks again :)

Best, Ken

Calder-Ty commented 1 year ago

Thanks This discussion was very helpful!

doums commented 1 year ago

Hi, @Calder-Ty it's been ages since I asked but to let you know I finally ended by implementing that part of the code in C. Not that your binding was wrong, I really think it's nice ;) But I found out it will be better for me to use the C lib straight + FFI. Anyway, thx for your work on this binding and for your time answering my questions!