l1npengtul / nokhwa

Cross Platform Rust Library for Powerful Webcam/Camera Capture
Apache License 2.0
502 stars 121 forks source link

CallbackCamera operations hang once camera stream is open #111

Open RReverser opened 1 year ago

RReverser commented 1 year ago

Simple repro:

use nokhwa::pixel_format::RgbFormat;
use nokhwa::utils::{RequestedFormat, RequestedFormatType};
use nokhwa::CallbackCamera;

fn main() -> anyhow::Result<()> {
    println!("Querying...");
    let cameras = nokhwa::query(nokhwa::utils::ApiBackend::Auto)?;
    println!("Opening camera...");
    let mut camera = CallbackCamera::new(
        cameras[0].index().clone(),
        RequestedFormat::new::<RgbFormat>(RequestedFormatType::AbsoluteHighestResolution),
        |_frame| {
            println!("Got frame!");
        },
    )?;
    camera.open_stream()?;
    println!("Waiting 1 sec...");
    std::thread::sleep(std::time::Duration::from_secs(1));
    println!("Trying to change format...");
    camera.set_camera_requset(RequestedFormat::new::<RgbFormat>(
        RequestedFormatType::AbsoluteHighestFrameRate,
    ))?;
    println!("Changed format successfully!");
    Ok(())
}

will currently output:

Querying...
Opening camera...
Waiting 1 sec...
Got frame!
Got frame!
Got frame!
Got frame!
Got frame!
Got frame!
Got frame!
Got frame!
Got frame!
Trying to change format...
Got frame!
Got frame!
Got frame!
Got frame!
Got frame!
Got frame!
Got frame!
Got frame!
Got frame!
Got frame!
Got frame!
Got frame!
Got frame!
Got frame!
Got frame!
Got frame!
Got frame!
Got frame!
[...]

That is, "Changed format successfully!" is never reached.

RReverser commented 1 year ago

What's interesting, I thought stopping stream and then changing camera format might help, but CallbackCamera::stop_stream itself also hangs:

use nokhwa::pixel_format::RgbFormat;
use nokhwa::utils::{RequestedFormat, RequestedFormatType};
use nokhwa::CallbackCamera;

fn main() -> anyhow::Result<()> {
    println!("Querying...");
    let cameras = nokhwa::query(nokhwa::utils::ApiBackend::Auto)?;
    println!("Opening camera...");
    let mut camera = CallbackCamera::new(
        cameras[0].index().clone(),
        RequestedFormat::new::<RgbFormat>(RequestedFormatType::AbsoluteHighestResolution),
        |_frame| {
            println!("Got frame!");
        },
    )?;
    camera.open_stream()?;
    println!("Waiting 1 sec...");
    std::thread::sleep(std::time::Duration::from_secs(1));
    println!("Trying to stop stream...");
    camera.stop_stream()?;
    println!("Stopped stream successfully!");
    Ok(())
}
Querying...
Opening camera...
Waiting 1 sec...
Got frame!
Got frame!
Got frame!
Got frame!
Got frame!
Got frame!
Got frame!
Got frame!
Trying to stop stream...
Got frame!
Got frame!
Got frame!
Got frame!
Got frame!
Got frame!
Got frame!
Got frame!
Got frame!
Got frame!
Got frame!
Got frame!
[...]

Looks like there's some lock issue in CallbackCamera that doesn't let it process any incoming messages once the stream is open.

RReverser commented 1 year ago

Looks like there's some lock issue in CallbackCamera that doesn't let it process any incoming messages once the stream is open.

Yeah I think the problem is here: https://github.com/l1npengtul/nokhwa/blob/12cfe670e89be6486f11efda7fa5b38c87233c4b/src/threaded.rs#L555-L556

This locks-unlocks camera in a tight loop, which is effectively the same as holding the lock forever outside the loop, so no other operation that tries to also take camera.lock() has a chance to get through.

RReverser commented 1 year ago

Semi-related question: I see you're using unsafe impl Send for Camera {}. Is it definitely safe for all camera backends?

I know some device drivers (not sure about webcams) really don't like being accessed from threads other than the one that created them, so I thought if they're not Send automatically, this might be problematic?