lit-robotics / libcamera-rs

Experimental Rust bindings for libcamera
Apache License 2.0
46 stars 15 forks source link

Unable to reuse `Requests` properly #4

Closed mfreeborn closed 1 year ago

mfreeborn commented 1 year ago

I'm just working on a very simple video recording proof of concept, structured on the jpeg_capture example. Here is the code:

use std::{process::exit, time::Duration};

use libcamera_rs::{
    camera::CameraConfigurationStatus, camera_manager::CameraManager, framebuffer_allocator::FrameBufferAllocator,
    framebuffer_map::MemoryMappedFrameBuffer, pixel_format::PixelFormat, properties, stream::StreamRole,
};

// drm-fourcc does not have MJPEG type yet, construct it from raw fourcc identifier
const PIXEL_FORMAT_MJPEG: PixelFormat = PixelFormat::new(u32::from_le_bytes([b'M', b'J', b'P', b'G']), 0);

fn main() {
    let filename = match std::env::args().nth(1) {
        Some(f) => f,
        None => {
            println!("Error: missing file output parameter");
            println!("Usage: ./video_capture </path/to/output.mjpeg>");
            exit(1);
        }
    };

    let mgr = CameraManager::new().unwrap();

    let cameras = mgr.cameras();

    let cam = cameras.get(0).expect("No cameras found");

    println!(
        "Using camera: {}",
        *cam.properties().get::<properties::Model>().unwrap()
    );

    let mut cam = cam.acquire().expect("Unable to acquire camera");

    // This will generate default configuration for each specified role
    let mut cfgs = cam.generate_configuration(&[StreamRole::VideoRecording]).unwrap();

    cfgs.get_mut(0).unwrap().set_pixel_format(PIXEL_FORMAT_MJPEG);

    println!("Generated config: {:#?}", cfgs);

    match cfgs.validate() {
        CameraConfigurationStatus::Valid => println!("Camera configuration valid!"),
        CameraConfigurationStatus::Adjusted => println!("Camera configuration was adjusted: {:#?}", cfgs),
        CameraConfigurationStatus::Invalid => panic!("Error validating camera configuration"),
    }

    // Ensure that pixel format was unchanged
    assert_eq!(
        cfgs.get(0).unwrap().get_pixel_format(),
        PIXEL_FORMAT_MJPEG,
        "MJPEG is not supported by the camera"
    );

    cam.configure(&mut cfgs).expect("Unable to configure camera");

    let mut alloc = FrameBufferAllocator::new(&cam);

    // Allocate frame buffers for the stream
    let cfg = cfgs.get(0).unwrap();
    let stream = cfg.stream().unwrap();
    let buffers = alloc.alloc(&stream).unwrap();
    println!("Allocated {} buffers", buffers.len());

    // Convert FrameBuffer to MemoryMappedFrameBuffer, which allows reading &[u8]
    let buffers = buffers
        .into_iter()
        .map(|buf| MemoryMappedFrameBuffer::new(buf).unwrap())
        .collect::<Vec<_>>();

    // Create capture requests and attach buffers
    let reqs = buffers
        .into_iter()
        .enumerate()
        .map(|(i, buf)| {
            let mut req = cam.create_request(Some(i as u64)).unwrap();
            req.add_buffer(&stream, buf).unwrap();
            req
        })
        .collect::<Vec<_>>();

    // Completed capture requests are returned as a callback
    let (tx, rx) = std::sync::mpsc::channel();
    cam.on_request_completed(move |req| {
        tx.send(req).unwrap();
    });

    // TODO: Set `Control::FrameDuration()` here. Blocked on https://github.com/lit-robotics/libcamera-rs/issues/2
    cam.start(None).unwrap();

    // Enqueue all requests to the camera
    for req in reqs {
        println!("Request queued for execution: {req:#?}");
        cam.queue_request(req).unwrap();
    }

    loop {
        println!("Waiting for camera request execution");
        let req = rx.recv_timeout(Duration::from_secs(2)).expect("Camera request failed");
        println!("Received frame {} from request {}", req.sequence(), req.cookie());

        // Recycle the request back to the camera for execution
        cam.queue_request(req).unwrap();
    }

    // Everything is cleaned up automatically by Drop implementations
}

The output is as follows:

[22:52:00.786683276] [53183]  INFO Camera camera_manager.cpp:299 libcamera v0.0.4+17-3f8bcc1a
Using camera: Integrated_Webcam_FHD: Integrat
Generated config: [
    StreamConfigurationRef {
        pixel_format: MJPEG,
        size: Size {
            width: 1920,
            height: 1080,
        },
        stride: 0,
        frame_size: 4147789,
        buffer_count: 4,
    },
]
Camera configuration valid!
[22:52:00.926189027] [53183]  INFO Camera camera.cpp:1028 configuring streams: (0) 1920x1080-MJPEG
Allocated 4 buffers
Request queued for execution: Request {
    seq: 0,
    status: Pending,
    cookie: 0,
}
Request queued for execution: Request {
    seq: 0,
    status: Pending,
    cookie: 1,
}
Request queued for execution: Request {
    seq: 0,
    status: Pending,
    cookie: 2,
}
Request queued for execution: Request {
    seq: 0,
    status: Pending,
    cookie: 3,
}
Waiting for camera request execution
Received frame 0 from request 0
Waiting for camera request execution
Received frame 1 from request 1
Waiting for camera request execution
Received frame 2 from request 2
Waiting for camera request execution
Received frame 3 from request 3
Waiting for camera request execution
Received frame 4 from request 0
Waiting for camera request execution
Received frame 5 from request 1
Waiting for camera request execution
Received frame 6 from request 2
Waiting for camera request execution
[22:52:01.551288812] [53300] FATAL default request.cpp:155 assertion "request->status() == RequestPending" failed in cancel()
Backtrace:
/usr/local/lib/x86_64-linux-gnu/libcamera.so.0.0.4(_ZN9libcamera7Request7Private6cancelEv+0xfb) [0x7f8835532d1d]
/usr/local/lib/x86_64-linux-gnu/libcamera.so.0.0.4(_ZN9libcamera15PipelineHandler14doQueueRequestEPNS_7RequestE+0xef) [0x7f8835526b71]
/usr/local/lib/x86_64-linux-gnu/libcamera.so.0.0.4(_ZN9libcamera15PipelineHandler15doQueueRequestsEv+0x55) [0x7f8835526bdf]
/usr/local/lib/x86_64-linux-gnu/libcamera.so.0.0.4(_ZN9libcamera17BoundMethodMemberINS_15PipelineHandlerEvJEE6invokeEv+0x74) [0x7f883552e026]
/usr/local/lib/x86_64-linux-gnu/libcamera-base.so.0.0.4(_ZN9libcamera15BoundMethodArgsIvJEE10invokePackIJEvEENSt9enable_ifIXsrSt7is_voidIT0_E5valueEvE4typeEPNS_19BoundMethodPackBaseESt16integer_sequenceImJXspT_EEE+0x33) [0x7f883573cf47]
/usr/local/lib/x86_64-linux-gnu/libcamera-base.so.0.0.4(_ZN9libcamera15BoundMethodArgsIvJEE10invokePackEPNS_19BoundMethodPackBaseE+0x27) [0x7f883573ce51]
/usr/local/lib/x86_64-linux-gnu/libcamera-base.so.0.0.4(_ZN9libcamera15BoundMethodBase12activatePackESt10shared_ptrINS_19BoundMethodPackBaseEEb+0x109) [0x7f883571f199]
/usr/local/lib/x86_64-linux-gnu/libcamera.so.0.0.4(_ZN9libcamera17BoundMethodMemberINS_15PipelineHandlerEvJEE8activateEb+0xd7) [0x7f883552df33]
/usr/local/lib/x86_64-linux-gnu/libcamera-base.so.0.0.4(_ZN9libcamera6SignalIJEE4emitEv+0x8c) [0x7f8835722b5e]
/usr/local/lib/x86_64-linux-gnu/libcamera.so.0.0.4(_ZN9libcamera7Request7Private20emitPrepareCompletedEv+0x28) [0x7f8835532df8]
/usr/local/lib/x86_64-linux-gnu/libcamera.so.0.0.4(_ZN9libcamera7Request7Private7prepareENSt6chrono8durationIlSt5ratioILl1ELl1000EEEE+0x17c) [0x7f8835532fa2]
/usr/local/lib/x86_64-linux-gnu/libcamera.so.0.0.4(_ZN9libcamera15PipelineHandler12queueRequestEPNS_7RequestE+0x5a) [0x7f8835526a7a]
/usr/local/lib/x86_64-linux-gnu/libcamera.so.0.0.4(_ZN9libcamera17BoundMethodMemberINS_15PipelineHandlerEvJPNS_7RequestEEE6invokeES3_+0x7f) [0x7f88354b4497]
target/debug/examples/video_capture(_ZN9libcamera15BoundMethodArgsIvJPNS_7RequestEEE10invokePackIJLm0EEvEENSt9enable_ifIXsrSt7is_voidIT0_E5valueEvE4typeEPNS_19BoundMethodPackBaseESt16integer_sequenceImJXspT_EEE+0x4a) [0x55e79a75d3d0]
target/debug/examples/video_capture(_ZN9libcamera15BoundMethodArgsIvJPNS_7RequestEEE10invokePackEPNS_19BoundMethodPackBaseE+0x27) [0x55e79a75d1b9]
/usr/local/lib/x86_64-linux-gnu/libcamera-base.so.0.0.4(_ZN9libcamera13InvokeMessage6invokeEv+0x46) [0x7f883573012e]
/usr/local/lib/x86_64-linux-gnu/libcamera-base.so.0.0.4(_ZN9libcamera6Object7messageEPNS_7MessageE+0x7a) [0x7f8835730876]
/usr/local/lib/x86_64-linux-gnu/libcamera-base.so.0.0.4(_ZN9libcamera6Thread16dispatchMessagesENS_7Message4TypeE+0x317) [0x7f8835737a8d]
/usr/local/lib/x86_64-linux-gnu/libcamera-base.so.0.0.4(_ZN9libcamera19EventDispatcherPoll13processEventsEv+0x38) [0x7f8835720d40]
/usr/local/lib/x86_64-linux-gnu/libcamera-base.so.0.0.4(_ZN9libcamera6Thread4execEv+0x70) [0x7f8835736d9e]
/usr/local/lib/x86_64-linux-gnu/libcamera.so.0.0.4(_ZN9libcamera13CameraManager7Private3runEv+0x112) [0x7f88354b65e8]
/usr/local/lib/x86_64-linux-gnu/libcamera-base.so.0.0.4(_ZN9libcamera6Thread11startThreadEv+0xf2) [0x7f8835736d22]
/usr/local/lib/x86_64-linux-gnu/libcamera-base.so.0.0.4(_ZSt13__invoke_implIvMN9libcamera6ThreadEFvvEPS1_JEET_St21__invoke_memfun_derefOT0_OT1_DpOT2_+0x6a) [0x7f883573b640]
/usr/local/lib/x86_64-linux-gnu/libcamera-base.so.0.0.4(_ZSt8__invokeIMN9libcamera6ThreadEFvvEJPS1_EENSt15__invoke_resultIT_JDpT0_EE4typeEOS6_DpOS7_+0x3b) [0x7f883573b593]
/usr/local/lib/x86_64-linux-gnu/libcamera-base.so.0.0.4(_ZNSt6thread8_InvokerISt5tupleIJMN9libcamera6ThreadEFvvEPS3_EEE9_M_invokeIJLm0ELm1EEEEvSt12_Index_tupleIJXspT_EEE+0x47) [0x7f883573b4f3]
/usr/local/lib/x86_64-linux-gnu/libcamera-base.so.0.0.4(_ZNSt6thread8_InvokerISt5tupleIJMN9libcamera6ThreadEFvvEPS3_EEEclEv+0x1c) [0x7f883573b4a8]
/usr/local/lib/x86_64-linux-gnu/libcamera-base.so.0.0.4(_ZNSt6thread11_State_implINS_8_InvokerISt5tupleIJMN9libcamera6ThreadEFvvEPS4_EEEEE6_M_runEv+0x20) [0x7f883573b488]
/lib/x86_64-linux-gnu/libstdc++.so.6(+0xdc2b3) [0x7f8834fb62b3]
Aborted (core dumped)

I believe the error is because the Requests are not getting cleared before re-queuing. The libcamera example cam app uses the Request::reuse method prior to re-queuing already-used Requests.

I think it would be handy if that method were exposed in these rust bindings.