Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to get camera frame on macbook #100

Open
bokutotu opened this issue Jan 24, 2023 · 30 comments
Open

Unable to get camera frame on macbook #100

bokutotu opened this issue Jan 24, 2023 · 30 comments
Assignees
Labels
AVFoundation related to AVFoundation (Apple) bug Something isn't working P2 High Priority (Non critical bugfix)
Milestone

Comments

@bokutotu
Copy link

bokutotu commented Jan 24, 2023

Thank you for creating such a great repository!
I have a issue when using Mabbook pro 14inc (M1 max).
Below are the steps to reproduce the error.

Ventura 13.1
machine MacBook Pro 14-inch 2021 Apple M1 Max
rust version 1.66.0
Cargo.toml

name = "show_image"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
show-image="0.13.1"
image = { version="0.24.5" }
nokhwa = { version = "0.10.3", features=["input-native", "output-threaded"] }

Code (main.rs)

use nokhwa::{
    nokhwa_initialize,
    pixel_format::{RgbAFormat, RgbFormat},
    query,
    utils::{ApiBackend, RequestedFormat, RequestedFormatType},
    threaded::CallbackCamera
};

fn main() {
    // only needs to be run on OSX
    nokhwa_initialize(|granted| {
        println!("User said {}", granted);
    });
    let cameras = query(ApiBackend::Auto).unwrap();
    cameras.iter().for_each(|cam| println!("{:?}", cam));

    let format = RequestedFormat::new::<RgbFormat>(RequestedFormatType::AbsoluteHighestFrameRate);

    let first_camera = cameras.first().unwrap();

    let mut threaded = CallbackCamera::new(first_camera.index().clone(), format, |buffer| {
        let image = buffer.decode_image::<RgbAFormat>().unwrap();
        println!("{}x{} {}", image.width(), image.height(), image.len());
    })
    .unwrap();
    threaded.open_stream().unwrap();
    #[allow(clippy::empty_loop)] // keep it running
    loop {
        let frame = threaded.poll_frame().unwrap();
        let image = frame.decode_image::<RgbAFormat>().unwrap();
        println!(
            "{}x{} {} naripoggers",
            image.width(),
            image.height(),
            image.len()
        );
    }
}

Error

User said true
CameraInfo { human_name: "FaceTime HD Camera", description: "Apple Inc.: FaceTime HD Camera - AVCaptureDeviceTypeBuiltInWideAngleCamera, Unspecified f0", misc: "47B4B64B-7067-4B9C-AD2B-AE273A71F4B5", index: Index(0) }
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: ProcessFrameError { src: NV12, destination: "RGB", error: "bad input buffer size" }', src/main.rs:63:56
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
thread '<unnamed>' panicked at 'called `Result::unwrap()` on an `Err` value: ProcessFrameError { src: NV12, destination: "RGB", error: "bad input buffer size" }', src/main.rs:55:57

If not already resolved, I would like to contribute.

@bokutotu bokutotu changed the title camera.info's resolution is different from number of camera.frame's slice elements Unable to get camera frame on macbook Jan 24, 2023
@l1npengtul l1npengtul self-assigned this Jan 30, 2023
@l1npengtul l1npengtul added bug Something isn't working P2 High Priority (Non critical bugfix) AVFoundation related to AVFoundation (Apple) labels Jan 30, 2023
@l1npengtul l1npengtul added this to the 0.11 milestone Jan 30, 2023
@bookshiyi
Copy link
Contributor

bookshiyi commented Jan 31, 2023

#72 (comment)
Source frame format NV12 may not be supported on macintosh untill now, I guess.

@l1npengtul
Copy link
Owner

It is likely a fault of the library improperly setting FourCC

@hallucinogen
Copy link

I found that the first problem is the NV12 checker in this line:

if data.len() != ((resolution.width() * resolution.height() * 3) / 2) as usize {

if data.len() != ((resolution.width() * resolution.height() * 3) / 2) as usize {

I tried to change it into

if data.len() != (resolution.width() * resolution.height() * 2) as usize {

Now it passes the decoding round.

But the resulting decoded image is faulty. It looks like it's split in the middle, and then we put the left part on the right and the right part on the left. I feel I am close to a solution, but can't really point it out

Screenshot 2023-03-19 at 18 36 57

@vaqxai
Copy link

vaqxai commented Mar 31, 2023

Does not work on Macbook Air 2014 13". Same error.

@stolinski
Copy link

Anyone have a work around for this? All cams in macOS are coming in as NV12 and hitting the same bad input buffer size issue.

@anselanza
Copy link

Damn, I just hit this problem, too.

Until NV12 support comes in v0.11, it's possible to use a crate like DCV Color Primitives to do the necessary conversion.

@bluezheng
Copy link

Hi, is there any workaround for this issue?

@marcpabst
Copy link

marcpabst commented Oct 3, 2023

Hi, I'm seeing this issue as well. I think the problem is that AVfoundation returns frames in UYVY format (https://wiki.videolan.org/YUV#UYVY), even if you tell nokhwa to request NV12 (or some other format).

Here is my current workaround to deal with this kind of data (note that this only works on Rust nightly). Alternativly, you could split the interleaved planes and use the aforementioned dcv-color-primitives crate for conversion.

#![feature(portable_simd)]
use std::simd::SimdFloat;
use std::simd::f32x4;
use rayon::prelude::*;

#[inline]
pub fn uyvy_to_rgb24(in_buf: &[u8], out_buf: &mut [u8]) {
    debug_assert!(out_buf.len() as f32 == in_buf.len() as f32 * 1.5);

    in_buf
        .par_chunks_exact(4) // FIXME: use par_array_chunks() when stabalized (https://github.com/rayon-rs/rayon/pull/789)
        .zip(out_buf.par_chunks_exact_mut(6))
        .for_each(|(ch, out)| {
            let y1 = ch[1];
            let y2 = ch[3];
            let cb = ch[0];
            let cr = ch[2];

            let (r, g, b) = ycbcr_to_rgb(y1, cb, cr);

            out[0] = r;
            out[1] = g;
            out[2] = b;

            let (r, g, b) = ycbcr_to_rgb(y2, cb, cr);

            out[3] = r;
            out[4] = g;
            out[5] = b;
        });
}

// COLOR CONVERSION: https://stackoverflow.com/questions/28079010/rgb-to-ycbcr-using-simd-vectors-lose-some-data

#[inline]
fn ycbcr_to_rgb(y: u8, cb: u8, cr: u8) -> (u8, u8, u8) {
    let ycbcr = f32x4::from_array([y as f32, cb as f32 - 128.0f32, cr as f32 - 128.0f32, 0.0]);

    // rec 709: https://mymusing.co/bt-709-yuv-to-rgb-conversion-color/
    let r = (ycbcr * f32x4::from_array([1.0, 0.00000, 1.5748, 0.0])).reduce_sum();
    let g = (ycbcr * f32x4::from_array([1.0, -0.187324, -0.468124, 0.0])).reduce_sum();
    let b = (ycbcr * f32x4::from_array([1.0, 1.8556, 0.00000, 0.0])).reduce_sum();

    (clamp(r), clamp(g), clamp(b))
}

#[inline]
fn clamp(val: f32) -> u8 {
    if val < 0.0 {
        0
    } else if val > 255.0 {
        255
    } else {
        val.round() as u8
    }
}

(adapted from here: https://gist.github.com/arifd/ea820ec97265a023e67a88b66955855d)

As a sidenote, it looks like recent MacOS versions do not support transparent raw access to the camera outputs anymore, i.e. you will always be offered uyvy422, yuyv422, nv12, 0rgb, and bgr0, regardless of what the camera actually supports. MJPEG streams will still work when requesting a resolution/fps mode only supported through MJPEG (as is common for higher resolutions on UVC cameras , but AVfoundation will handle the conversion to one of the listed pixel formats internally.

@l1npengtul, not sure what the current status is here (I'm a bit confused with the different branches) but I might be able to help you with debugging this.

Edit: Forgot to say that this is on a M2 MacBook Pro on Ventura 13.4.

@yamt
Copy link
Contributor

yamt commented Oct 17, 2023

Hi, I'm seeing this issue as well. I think the problem is that AVfoundation returns frames in UYVY format (https://wiki.videolan.org/YUV#UYVY), even if you tell nokhwa to request NV12 (or some other format).

see #151

@Moon1102
Copy link

@l1npengtul Could you take a look at the code below when you have a moment? This is a test case I wrote where I was able to convert a camera data frame into an image and save it, on Rust v1.79, MacOS 13.5.2 (Apple M1). The only problem now is that the color is bluish, I don't know why yet, this code works well and the color is correct when I use the av-foundation library.

#[cfg(test)]
mod nokhwa_camera_macos {
    use image::{ImageBuffer, Rgb};
    use nokhwa::{Buffer, nokhwa_initialize, NokhwaError, pixel_format::RgbFormat, query, threaded::CallbackCamera, utils::{ApiBackend, RequestedFormat, RequestedFormatType}};
    use nokhwa::utils::Resolution;

    #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
    async fn test() -> Result<(), NokhwaError> {
        // only needs to be run on OSX
        nokhwa_initialize(|granted| {
            println!("User said {}", granted);
        });
        let cameras = query(ApiBackend::AVFoundation)?;

        let format = RequestedFormat::new::<RgbFormat>(RequestedFormatType::AbsoluteHighestFrameRate);

        let first_camera = cameras.first().ok_or(NokhwaError::GeneralError("cannot find camera".to_string()))?;

        let mut threaded = CallbackCamera::new(first_camera.index().clone(), format, |buffer| {
            convert_buffer_to_image(buffer).map(|buf|
                buf.save("src/output/output.png")
            ).ok();
        })?;
        threaded.open_stream()?;
        #[allow(clippy::empty_loop)] // keep it running
        loop {
            threaded.poll_frame()?;
        }
    }

    fn convert_buffer_to_image(buffer: Buffer) -> Result<ImageBuffer<Rgb<u8>, Vec<u8>>, NokhwaError> {
        let Resolution { width_x: width, height_y: height } = buffer.resolution();
        let mut image_buffer = ImageBuffer::<Rgb<u8>, Vec<u8>>::new(width, height);
        let data = buffer.buffer();

        for (y, chunk) in data.chunks_exact((width * 2) as usize).enumerate().take(height as usize) {
            for (x, pixel) in chunk.chunks_exact(4).enumerate() {
                let [v, y2, u, y1] = [pixel[0] as f32, pixel[1] as f32, pixel[2] as f32, pixel[3] as f32];
                let x = (x * 2) as u32;
                image_buffer.put_pixel(x, y as u32, yuv_to_rgb(y1, u, v));
                image_buffer.put_pixel(x + 1, y as u32, yuv_to_rgb(y2, u, v));
            }
        }

        Ok(image_buffer)
    }

    //YUV to RGB conversion BT.709
    fn yuv_to_rgb(y: f32, u: f32, v: f32) -> Rgb<u8> {
        let r = y + 1.5748 * (v - 128.0);
        let g = y - 0.1873 * (u - 128.0) - 0.4681 * (v - 128.0);
        let b = y + 1.8556 * (u - 128.0);

        Rgb([r as u8, g as u8, b as u8])
    }
}

@l1npengtul
Copy link
Owner

Honestly, the current avfoundation code is in such a state that Ive blanked it out of my mind. I really cant help you here, unfortunately.

I'll work on making nokhwa itself use avfoundation.

@Moon1102
Copy link

Okay, I just wanted to say that I found a way to start by replacing the code let image = buffer.decode_image::<RgbAFormat>().unwrap(); temporarily with my code, so that I can successfully start using nokhwa, otherwise I would have to accept the NV12: bad input buffer size error. I can't say I've managed to solve the problem completely, but it's going in a good direction for me~ I'll take another look at how to fix the bluish pictures sometime soon.

@Moon1102
Copy link

Okay, I just wanted to say that I found a way to start by replacing the code let image = buffer.decode_image::<RgbAFormat>().unwrap(); temporarily with my code, so that I can successfully start using nokhwa, otherwise I would have to accept the NV12: bad input buffer size error. I can't say I've managed to solve the problem completely, but it's going in a good direction for me~ I'll take another look at how to fix the bluish pictures sometime soon.

Just change
let [v, y2, u, y1] = [pixel[0] as f32, pixel[1] as f32, pixel[2] as f32, pixel[3] as f32];
to
let [u, y1, v, y2] = [pixel[0] as f32, pixel[1] as f32, pixel[2] as f32, pixel[3] as f32];
so you can get the image with the correct color.

@raphaelmenges
Copy link

raphaelmenges commented Jul 2, 2024

Just change
let [v, y2, u, y1] = [pixel[0] as f32, pixel[1] as f32, pixel[2] as f32, pixel[3] as f32];
to
let [u, y1, v, y2] = [pixel[0] as f32, pixel[1] as f32, pixel[2] as f32, pixel[3] as f32];
so you can get the image with the correct color.

Thanks @Moon1102 for the proposed workaround. It works on my MacBook for the integrated camera, with M1 CPU and macOS 14.5! Do you know whether this affects only Macs with Apple Silicon or all Macs? Or does it depend on the camera?

@Moon1102
Copy link

Moon1102 commented Jul 3, 2024

@raphaelmenges Sorry, at the moment I only have a MacBook with an Apple Silicon M1 and an integrated camera, so I'm not sure if it will work properly on other types of Macs. I think the code for nokhwa's codecs may need further improvement in terms of adapting to individual devices now, perhaps will better after version 0.11. Also, I'm curious if you're experiencing error #165? i haven't found a solution yet.

@paviro
Copy link

paviro commented Jul 4, 2024

@Moon1102 using your code I still get this result:
output
Any idea? This is a USB capture card connected to the Mac itself.

@Moon1102
Copy link

Moon1102 commented Jul 5, 2024

@Moon1102 using your code I still get this result: output Any idea? This is a USB capture card connected to the Mac itself.

@paviro Hi. My camera captures video frames of type kCVPixelFormatType_422YpCbCr8 aka "2vuy". There are various types of them, which you can refer to it to determine the type of your own video frames. And this example can be used to analyze and determine your video frame type.

@paviro
Copy link

paviro commented Jul 5, 2024

Thank you for the tips @Moon1102 will investigate further!

@paviro
Copy link

paviro commented Jul 9, 2024

@Moon1102 My camera also seems to output 2vuy. Not sure what I am doing wrong? Could you give me any more hints? Totally fine if you don't want to sadly I am a bit new to rust and the video world.

pixel buffer: "<CVPixelBuffer 0x600001c8f4f0 width=1920 height=1080 bytesPerRow=3840 pixelFormat=2vuy iosurface=0x6000029ecf10 surfaceid=2146 attributes={
    Height = 1080;
    IOSurfaceProperties =     {
        IOSurfacePurgeWhenNotInUse = 1;
    };
    PixelFormatType = 846624121;
    Width = 1920;
} propagatedAttachments={
    CGColorSpace = "<CGColorSpace 0x600000fed3e0> (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; Composite NTSC)";
    CVImageBufferColorPrimaries = "SMPTE_C";
    CVImageBufferTransferFunction = "ITU_R_709_2";
    CVImageBufferYCbCrMatrix = "ITU_R_601_4";
} nonPropagatedAttachments={
}>"

@paviro
Copy link

paviro commented Jul 9, 2024

Well for now I just patched raw_fcc_to_frameformat in nokhwa-bindings-macos/src/lib.rs to return FrameFormat::YUYV for 875704438 instead of FrameFormat::NV12.

fn raw_fcc_to_frameformat(raw: OSType) -> Option<FrameFormat> {
        match raw {
            kCMVideoCodecType_422YpCbCr8 | kCMPixelFormat_422YpCbCr8_yuvs => {
                Some(FrameFormat::YUYV)
            }
            kCMVideoCodecType_JPEG | kCMVideoCodecType_JPEG_OpenDML => Some(FrameFormat::MJPEG),
            kCMPixelFormat_8IndexedGray_WhiteIsZero => Some(FrameFormat::GRAY),
            kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange
            | kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
            | 875704438 => Some(FrameFormat::YUYV),
            kCMPixelFormat_24RGB => Some(FrameFormat::RAWRGB),
            _ => None,
        }
    }

Now simply doing this works without any extra steps and result in a correct image:

let frame = camera.frame()?;
let decoded = frame.decode_image::<RgbFormat>().unwrap();

let output_path = Path::new("output.jpg");
decoded.save(output_path)?;

@Moon1102
Copy link

@Moon1102 My camera also seems to output 2vuy. Not sure what I am doing wrong? Could you give me any more hints? Totally fine if you don't want to sadly I am a bit new to rust and the video world.

pixel buffer: "<CVPixelBuffer 0x600001c8f4f0 width=1920 height=1080 bytesPerRow=3840 pixelFormat=2vuy iosurface=0x6000029ecf10 surfaceid=2146 attributes={
    Height = 1080;
    IOSurfaceProperties =     {
        IOSurfacePurgeWhenNotInUse = 1;
    };
    PixelFormatType = 846624121;
    Width = 1920;
} propagatedAttachments={
    CGColorSpace = "<CGColorSpace 0x600000fed3e0> (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; Composite NTSC)";
    CVImageBufferColorPrimaries = "SMPTE_C";
    CVImageBufferTransferFunction = "ITU_R_709_2";
    CVImageBufferYCbCrMatrix = "ITU_R_601_4";
} nonPropagatedAttachments={
}>"

@paviro Sorry, I just saw the message and found that you've already solved the problem in a simpler and better way. That's awesome. I debugged it and got a format value of 846624121, which is different from yours. If your value is 875704438, that corresponds to kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange and it doesn't matter anymore.

@paviro
Copy link

paviro commented Jul 10, 2024

I don't fully understand as you can see the av-foundation binding does return 846624121 but somehow the same camera in nokhwa seems to return 875704438. Anyhow glad this works for now! Thank you for your support!

@shadrach-tayo
Copy link

@Moon1102 My camera also seems to output 2vuy. Not sure what I am doing wrong? Could you give me any more hints? Totally fine if you don't want to sadly I am a bit new to rust and the video world.

pixel buffer: "<CVPixelBuffer 0x600001c8f4f0 width=1920 height=1080 bytesPerRow=3840 pixelFormat=2vuy iosurface=0x6000029ecf10 surfaceid=2146 attributes={
    Height = 1080;
    IOSurfaceProperties =     {
        IOSurfacePurgeWhenNotInUse = 1;
    };
    PixelFormatType = 846624121;
    Width = 1920;
} propagatedAttachments={
    CGColorSpace = "<CGColorSpace 0x600000fed3e0> (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; Composite NTSC)";
    CVImageBufferColorPrimaries = "SMPTE_C";
    CVImageBufferTransferFunction = "ITU_R_709_2";
    CVImageBufferYCbCrMatrix = "ITU_R_601_4";
} nonPropagatedAttachments={
}>"

@paviro Sorry, I just saw the message and found that you've already solved the problem in a simpler and better way. That's awesome. I debugged it and got a format value of 846624121, which is different from yours. If your value is 875704438, that corresponds to kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange and it doesn't matter anymore.

Running into the same issue and I've applied the same patch, I get the same pixel format type 875704438 when using nokhwa, but when I decode to jpeg I still get this
webcam

so I applied this function you posted here #100 (comment) to the raw buffer and saved it as jpg and got this dark image under bright light
custom_image

I'm on a M3 pro max btw, I'd appreciate it If anyone can help in anyway @paviro @Moon1102 @l1npengtul

@l1npengtul
Copy link
Owner

I'll see what I can do, but this will likely be fixed in 0.11 since we are switching to the avfoundation bindings crate instead of the current error prone msg send implementation.

@l1npengtul
Copy link
Owner

It also doesn't help I don't have a mac to test this with... up to this point mac support was mostly me Winging It(tm)

@shadrach-tayo
Copy link

I'll see what I can do, but this will likely be fixed in 0.11 since we are switching to the avfoundation bindings crate instead of the current error prone msg send implementation.

Thanks for your good work 🙏🏽, Is there any temporary solution around this? It's been kicking my ass for a while now. I even tried ffmpeg to raw image conversion and still got the same result.

How soon is 0.11 going to be out? I'd love to help anyway I can too

@shadrach-tayo
Copy link

when I set the capture device to my iPhone camera I get a clear, blurred or dark image sometimes
convert_buffer_to_image copy
custom_image copy

I suspect the camera/video setting device configuration also can be an issue on Macos/Iphone

@l1npengtul
Copy link
Owner

l1npengtul commented Oct 23, 2024

Thanks for your good work 🙏🏽, Is there any temporary solution around this? It's been kicking my ass for a while now. I even tried ffmpeg to raw image conversion and still got the same result.

Its probably AVFoundation backend and improperly set FrameFormat values.

How soon is 0.11 going to be out? I'd love to help anyway I can too

Follow tracking issue #86

I suspect the camera/video setting device configuration also can be an issue on Macos/Iphone

I suspect the blurry image is due to auto focus.

@shadrach-tayo
Copy link

Thanks for your good work 🙏🏽, Is there any temporary solution around this? It's been kicking my ass for a while now. I even tried ffmpeg to raw image conversion and still got the same result.

Its probably AVFoundation backend and improperly set FrameFormat values.

How soon is 0.11 going to be out? I'd love to help anyway I can too

Follow tracking issue #86

I suspect the camera/video setting device configuration also can be an issue on Macos/Iphone

I suspect the blurry image is due to auto focus.

I'll keep tracking the issue in the mean time, thanks!

@astraw
Copy link

astraw commented Dec 23, 2024

I followed the idea from @paviro above and made a PR with a fix (#193) that works on my M1 Macbook Pro. You can use it already by using this in your [dependencies] in Cargo.toml:

nokhwa = { git = "https://github.com/astraw/nokhwa", rev = "0d09cc32c6bd06325115b2542cf58afc6f9eca8f", features = [
    "input-native",
] }

l1npengtul pushed a commit that referenced this issue Dec 23, 2024
kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange
kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange

Fixes #100.

This was suggested by the code from @paviro at
#100 (comment).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
AVFoundation related to AVFoundation (Apple) bug Something isn't working P2 High Priority (Non critical bugfix)
Projects
None yet
Development

No branches or pull requests