Files
nokhwa/src/utils.rs
T
2021-06-01 13:42:25 +09:00

449 lines
15 KiB
Rust

use crate::NokhwaError;
use mozjpeg::Decompress;
use std::fmt::Formatter;
use std::{cmp::Ordering, convert::TryFrom, fmt::Display, slice::from_raw_parts};
/// Describes a frame format (i.e. how the bytes themselves are encoded). Often called `FourCC` <br>
/// YUYV is a mathematical color space. You can read more [here.](https://en.wikipedia.org/wiki/YCbCr) <br>
/// MJPEG is a motion-jpeg compressed frame, it allows for high frame rates.
#[derive(Copy, Clone, Debug, PartialEq, Hash, PartialOrd, Ord, Eq)]
pub enum FrameFormat {
MJPEG,
YUYV,
}
impl Display for FrameFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FrameFormat::MJPEG => {
write!(f, "MJPEG")
}
FrameFormat::YUYV => {
write!(f, "YUYV")
}
}
}
}
/// Describes a Resolution.
/// This struct consists of a Width and a Height value (x,y). <br>
/// Note: the [`Ord`] implementation of this struct is flipped from highest to lowest.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct Resolution {
pub width_x: u32,
pub height_y: u32,
}
impl Resolution {
/// Create a new resolution from 2 image size coordinates.
pub fn new(x: u32, y: u32) -> Self {
Resolution {
width_x: x,
height_y: y,
}
}
/// Get the width of Resolution
pub fn width(self) -> u32 {
self.width_x
}
/// Get the height of Resolution
pub fn height(self) -> u32 {
self.height_y
}
/// Get the x (width) of Resolution
pub fn x(self) -> u32 {
self.width_x
}
/// Get the y (height) of Resolution
pub fn y(self) -> u32 {
self.height_y
}
}
impl Display for Resolution {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}x{}", self.x(), self.y())
}
}
impl PartialOrd for Resolution {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Resolution {
fn cmp(&self, other: &Self) -> Ordering {
match self.x().cmp(&other.x()) {
Ordering::Less => Ordering::Less,
Ordering::Equal => self.y().cmp(&other.y()),
Ordering::Greater => Ordering::Greater,
}
}
}
/// This is a convenience struct that holds all information about the format of a webcam stream.
/// It consists of a [`Resolution`], [`FrameFormat`], and a framerate(u8).
#[derive(Copy, Clone, Debug, Hash, PartialEq)]
pub struct CameraFormat {
resolution: Resolution,
format: FrameFormat,
framerate: u32,
}
impl CameraFormat {
/// Construct a new [`CameraFormat`]
pub fn new(resolution: Resolution, format: FrameFormat, framerate: u32) -> Self {
CameraFormat {
resolution,
format,
framerate,
}
}
/// [`CameraFormat::new()`], but raw.
pub fn new_from(res_x: u32, res_y: u32, format: FrameFormat, fps: u32) -> Self {
CameraFormat {
resolution: Resolution {
width_x: res_x,
height_y: res_y,
},
format,
framerate: fps,
}
}
/// Get the resolution of the current [`CameraFormat`]
pub fn resolution(&self) -> Resolution {
self.resolution
}
/// Get the width of the resolution of the current [`CameraFormat`]
pub fn width(&self) -> u32 {
self.resolution.width()
}
/// Get the height of the resolution of the current [`CameraFormat`]
pub fn height(&self) -> u32 {
self.resolution.height()
}
/// Set the [`CameraFormat`]'s resolution.
pub fn set_resolution(&mut self, resolution: Resolution) {
self.resolution = resolution;
}
/// Get the framerate of the current [`CameraFormat`]
pub fn framerate(&self) -> u32 {
self.framerate
}
/// Set the [`CameraFormat`]'s framerate.
pub fn set_framerate(&mut self, framerate: u32) {
self.framerate = framerate;
}
/// Get the [`CameraFormat`]'s format.
pub fn format(&self) -> FrameFormat {
self.format
}
/// Set the [`CameraFormat`]'s format.
pub fn set_format(&mut self, format: FrameFormat) {
self.format = format;
}
}
impl Default for CameraFormat {
fn default() -> Self {
CameraFormat {
resolution: Resolution::new(640, 480),
format: FrameFormat::MJPEG,
framerate: 15,
}
}
}
impl Display for CameraFormat {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}@{}FPS, {} Format",
self.resolution, self.framerate, self.format
)
}
}
/// Information about a Camera e.g. its name.
/// `description` amd `misc` may contain backend-specific information.
/// `index` is a camera's index given to it by (usually) the OS usually in the order it is known to the system.
#[derive(Clone, Debug, Default, Hash, PartialEq, Eq)]
pub struct CameraInfo {
human_name: String,
description: String,
misc: String,
index: usize,
}
impl CameraInfo {
/// Create a new [`CameraInfo`].
pub fn new(human_name: String, description: String, misc: String, index: usize) -> Self {
CameraInfo {
human_name,
description,
misc,
index,
}
}
/// Get a reference to the device info's human name.
pub fn human_name(&self) -> &String {
&self.human_name
}
/// Set the device info's human name.
pub fn set_human_name(&mut self, human_name: String) {
self.human_name = human_name;
}
/// Get a reference to the device info's description.
pub fn description(&self) -> &String {
&self.description
}
/// Set the device info's description.
pub fn set_description(&mut self, description: String) {
self.description = description;
}
/// Get a reference to the device info's misc.
pub fn misc(&self) -> &String {
&self.misc
}
/// Set the device info's misc.
pub fn set_misc(&mut self, misc: String) {
self.misc = misc;
}
/// Get a reference to the device info's index.
pub fn index(&self) -> &usize {
&self.index
}
/// Set the device info's index.
pub fn set_index(&mut self, index: usize) {
self.index = index;
}
}
impl PartialOrd for CameraInfo {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for CameraInfo {
fn cmp(&self, other: &Self) -> Ordering {
self.index.cmp(&other.index)
}
}
impl Display for CameraInfo {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Name: {}, Description: {}, Extra: {}, Index: {}",
self.human_name, self.description, self.misc, self.index
)
}
}
/// The list of known capture backends to the library. <br>
/// **Note: Only V4L2 and UVC (and by extension AUTO) is implemented so far.**
/// - AUTO is special - it tells the Camera struct to automatically choose a backend most suited for the current platform.
/// - V4L2 - `Video4Linux2`, a linux specific backend.
/// - UVC - Universal Video Class (please check [libuvc](https://github.com/libuvc/libuvc)). Platform agnostic, although on linux it needs `sudo` permissions or similar to use.
/// - Windows - Directshow, Windows only
/// - `OpenCV` - Uses `OpenCV` to capture. Platform agnostic.
/// - FFMPEG - Uses FFMPEG (libavdevice) to capture. Platform agnostic.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum CaptureAPIBackend {
Auto,
Video4Linux,
UniversalVideoClass,
Windows,
OpenCv,
Ffmpeg,
}
impl Display for CaptureAPIBackend {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let self_str = format!("{:?}", self);
write!(f, "{}", self_str)
}
}
/// Converts a MJPEG stream of [u8] into a Vec<u8> of RGB888. (R,G,B,R,G,B,...)
/// # Errors
/// If `mozjpeg` fails to read scanlines or setup the decompressor, this will error.
/// # Safety
/// This function uses `unsafe`. The caller must ensure that:
/// - The input data is of the right size, does not exceed bounds, and/or the final size matches with the initial size.
pub fn mjpeg_to_rgb888(data: &[u8]) -> Result<Vec<u8>, NokhwaError> {
let mut mozjpeg_decomp = match Decompress::new_mem(data) {
Ok(decomp) => match decomp.rgb() {
Ok(decompresser) => decompresser,
Err(why) => {
return Err(NokhwaError::CouldntDecompressFrame {
src: FrameFormat::MJPEG,
destination: "RGB888".to_string(),
error: why.to_string(),
})
}
},
Err(why) => {
return Err(NokhwaError::CouldntDecompressFrame {
src: FrameFormat::MJPEG,
destination: "RGB888".to_string(),
error: why.to_string(),
})
}
};
let decompressed = match mozjpeg_decomp.read_scanlines::<[u8; 3]>() {
Some(pixels) => pixels,
None => {
return Err(NokhwaError::CouldntDecompressFrame {
src: FrameFormat::MJPEG,
destination: "RGB888".to_string(),
error: "Failed to get read readlines into RGB888 pixels!".to_string(),
})
}
};
Ok(unsafe { from_raw_parts(decompressed.as_ptr().cast(), decompressed.len() * 3) }.to_vec())
}
// For those maintaining this, I recommend you read: https://docs.microsoft.com/en-us/windows/win32/medfound/recommended-8-bit-yuv-formats-for-video-rendering#yuy2
// https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB
// and this too: https://stackoverflow.com/questions/16107165/convert-from-yuv-420-to-imagebgr-byte
// The YUY2(YUYV) format is a 16 bit format. We read 4 bytes at a time to get 6 bytes of RGB888.
// First, the YUY2 is converted to YCbCr 4:4:4 (4:2:2 -> 4:4:4)
// then it is converted to 6 bytes (2 pixels) of RGB888
/// Converts a YUYV 4:2:2 datastream to a RGB888 Stream. [For further reading](https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB)
/// # Errors
/// This may error when the data stream size is not divisible by 4, a i32 -> u8 conversion fails, or it fails to read from a certain index.
pub fn yuyv422_to_rgb888(data: &[u8]) -> Result<Vec<u8>, NokhwaError> {
let mut rgb_vec: Vec<u8> = vec![];
if data.len() % 4 == 0 {
for px_idx in (0..data.len()).step_by(4) {
let u = match data.get(px_idx) {
Some(px) => match i32::try_from(*px) {
Ok(i) => i,
Err(why) => {
return Err(NokhwaError::CouldntDecompressFrame{ src: FrameFormat::YUYV, destination: "RGB888".to_string(), error: format!("Failed to convert byte at {} to a i32 because {}, This shouldn't happen!", px_idx, why.to_string()) });
}
},
None => {
return Err(NokhwaError::CouldntDecompressFrame {
src: FrameFormat::YUYV,
destination: "RGB888".to_string(),
error: format!(
"Failed to get bytes at {}, this is probably a bug, please report!",
px_idx
),
});
}
};
let y1 = match data.get(px_idx + 1) {
Some(px) => match i32::try_from(*px) {
Ok(i) => i,
Err(why) => {
return Err(NokhwaError::CouldntDecompressFrame{ src: FrameFormat::YUYV, destination: "RGB888".to_string(), error: format!("Failed to convert byte at {} to a i32 because {}, This shouldn't happen!", px_idx+1, why.to_string()) });
}
},
None => {
return Err(NokhwaError::CouldntDecompressFrame {
src: FrameFormat::YUYV,
destination: "RGB888".to_string(),
error: format!(
"Failed to get bytes at {}, this is probably a bug, please report!",
px_idx + 1
),
});
}
};
let v = match data.get(px_idx + 2) {
Some(px) => match i32::try_from(*px) {
Ok(i) => i,
Err(why) => {
return Err(NokhwaError::CouldntDecompressFrame{ src: FrameFormat::YUYV, destination: "RGB888".to_string(), error: format!("Failed to convert byte at {} to a i32 because {}, This shouldn't happen!", px_idx+2, why.to_string()) });
}
},
None => {
return Err(NokhwaError::CouldntDecompressFrame {
src: FrameFormat::YUYV,
destination: "RGB888".to_string(),
error: format!(
"Failed to get bytes at {}, this is probably a bug, please report!",
px_idx + 2
),
});
}
};
let y2 = match data.get(px_idx + 3) {
Some(px) => match i32::try_from(*px) {
Ok(i) => i,
Err(why) => {
return Err(NokhwaError::CouldntDecompressFrame{ src: FrameFormat::YUYV, destination: "RGB888".to_string(), error: format!("Failed to convert byte at {} to a i32 because {}, This shouldn't happen!", px_idx+3, why.to_string()) });
}
},
None => {
return Err(NokhwaError::CouldntDecompressFrame {
src: FrameFormat::YUYV,
destination: "RGB888".to_string(),
error: format!(
"Failed to get bytes at {}, this is probably a bug, please report!",
px_idx + 3
),
});
}
};
let pixel1 = yuyv444_to_rgb888(y1, u, v);
let pixel2 = yuyv444_to_rgb888(y2, u, v);
rgb_vec.append(&mut pixel1.to_vec());
rgb_vec.append(&mut pixel2.to_vec());
}
Ok(rgb_vec)
} else {
Err(NokhwaError::CouldntDecompressFrame {
src: FrameFormat::YUYV,
destination: "RGB888".to_string(),
error: "Assertion failure, the YUV stream isn't 4:2:2! (wrong number of bytes)"
.to_string(),
})
}
}
// equation from https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB
/// Convert `YCbCr` 4:4:4 to a RGB888. [For further reading](https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB)
#[allow(clippy::many_single_char_names)]
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_sign_loss)]
pub fn yuyv444_to_rgb888(y: i32, u: i32, v: i32) -> [u8; 3] {
let c298 = (y - 16) * 298;
let d = u - 128;
let e = v - 128;
let r = ((c298 + 409 * e + 128) >> 8).clamp(0, 255) as u8;
let g = ((c298 - 100 * d - 208 * e + 128) >> 8).clamp(0, 255) as u8;
let b = ((c298 + 516 * d + 128) >> 8).clamp(0, 255) as u8;
[r, g, b]
}