finished AVFoundation base update, prepare for CameraControl update

This commit is contained in:
l1npengtul
2022-09-29 09:21:29 -07:00
parent 642834cb79
commit 7c274634f4
12 changed files with 122 additions and 109 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Clippy Main" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="clippy --features &quot;output-wgpu&quot;" />
<option name="command" value="clippy --features &quot;output-wgpu, input-avfoundation&quot;" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<option name="channel" value="STABLE" />
<option name="requiredFeatures" value="true" />
+48 -41
View File
@@ -198,13 +198,14 @@ use objc::{
runtime::{Class, Object, Protocol, Sel, BOOL, YES},
};
use once_cell::sync::Lazy;
use std::collections::HashSet;
use std::{
borrow::Cow,
cmp::Ordering,
collections::HashSet,
convert::TryFrom,
error::Error,
ffi::{c_void, CStr, CString},
ffi::{c_void, CStr},
sync::{Arc, Mutex},
};
const UTF8_ENCODING: usize = 4;
@@ -325,8 +326,6 @@ fn compare_ns_string(this: *mut Object, other: core_media::NSString) -> bool {
}
}
fn default_callback(_: bool) {}
pub type CompressionData<'a> = (Cow<'a, [u8]>, FrameFormat);
pub type DataPipe<'a> = (Sender<CompressionData<'a>>, Receiver<CompressionData<'a>>);
@@ -335,14 +334,21 @@ static CALLBACK_CLASS: Lazy<&'static Class> = Lazy::new(|| {
let mut decl = ClassDecl::new("MyCaptureCallback", class!(NSObject)).unwrap();
// frame stack
decl.add_ivar::<*mut c_void>("_fnptr"); // oooh scary provenannce-breaking BULLSHIT AAAAAA I LOVE TYPE ERASURE
// oooh scary provenannce-breaking BULLSHIT AAAAAA I LOVE TYPE ERASURE
decl.add_ivar::<*const c_void>("_arcmutptr"); // ArkMutex, the not-arknights totally not gacha totally not ripoff new vidya game from l-pleasestop-npengtul
// KILL ME KILL ME KILL ME PLEASE KILL ME I DONT WANT TO LIVE ANYMORE
// i draw myself getting hurt and murdered in various ways to distract from my urges self harm
extern "C" fn my_callback_get_fnptr(this: &Object, _: Sel) -> *mut c_void {
unsafe { *this.get_ivar("_fnptr") }
extern "C" fn my_callback_get_arcmutptr(this: &Object, _: Sel) -> *const c_void {
unsafe { *this.get_ivar("_arcmutptr") }
}
extern "C" fn my_callback_set_fnptr(this: &mut Object, _: Sel, new_fnptr: *mut c_void) {
extern "C" fn my_callback_set_arcmutptr(
this: &mut Object,
_: Sel,
new_arcmutptr: *const c_void,
) {
unsafe {
this.set_ivar("_fnptr", new_fnptr);
this.set_ivar("_arcmutptr", new_arcmutptr);
}
}
@@ -383,18 +389,18 @@ static CALLBACK_CLASS: Lazy<&'static Class> = Lazy::new(|| {
unsafe { CVPixelBufferUnlockBaseAddress(image_buffer, 0) };
// oooooh scarey unsafe
// FIXME: FnMut is not boundary safe.
// Revery back fo `fn()`
let function_cvoid: *mut c_void = unsafe { msg_send![this, fnptr] };
let mut boxed_fn = unsafe {
// AAAAAAAAAAAAAAAAAAAAAAAAA
// https://c.tenor.com/0e_zWtFLOzQAAAAC/needy-streamer-overload-needy-girl-overdose.gif
std::mem::transmute_copy::<
*mut c_void,
Box<dyn FnMut(Vec<u8>, FrameFormat) + Send + Sync + 'static>,
>(&function_cvoid)
// AAAAAAAAAAAAAAAAAAAAAAAAA
// https://c.tenor.com/0e_zWtFLOzQAAAAC/needy-streamer-overload-needy-girl-overdose.gif
let bufferlck_cv: *mut c_void = unsafe { msg_send![this, bufferPtr] };
let buffer_mutex = unsafe {
std::mem::transmute::<*const c_void, Arc<Mutex<(Vec<u8>, FrameFormat)>>>(
bufferlck_cv,
)
};
boxed_fn(buffer_as_vec, fourcc);
let lock = buffer_mutex.lock();
if let Ok(mut buffer) = lock {
*buffer = (buffer_as_vec, fourcc);
}
}
#[allow(non_snake_case)]
@@ -409,12 +415,12 @@ static CALLBACK_CLASS: Lazy<&'static Class> = Lazy::new(|| {
unsafe {
decl.add_method(
sel!(fnptr),
my_callback_get_fnptr as extern "C" fn(&Object, Sel) -> *mut c_void,
sel!(bufferPtr),
my_callback_get_arcmutptr as extern "C" fn(&Object, Sel) -> *const c_void,
);
decl.add_method(
sel!(setFnPtr:),
my_callback_set_fnptr as extern "C" fn(&mut Object, Sel, *mut c_void),
sel!(SetBufferPtr:),
my_callback_set_arcmutptr as extern "C" fn(&mut Object, Sel, *const c_void),
);
decl.add_method(
sel!(captureOutput:didOutputSampleBuffer:fromConnection:),
@@ -659,16 +665,18 @@ pub struct AVCaptureVideoCallback {
impl AVCaptureVideoCallback {
pub fn new(
device_spec: &CStr,
callback: Box<dyn FnMut(Vec<u8>, FrameFormat) + Send + Sync + 'static>,
buffer: Arc<Mutex<(Vec<u8>, FrameFormat)>>,
) -> Result<Self, NokhwaError> {
let cls = &CALLBACK_CLASS as &Class;
let delegate: *mut Object = unsafe { msg_send![cls, alloc] };
let delegate: *mut Object = unsafe { msg_send![delegate, init] };
let fnptr_pinned = Box::leak(callback);
let buffer_as_ptr = unsafe {
std::mem::transmute::<*const Mutex<(Vec<u8>, FrameFormat)>, *const c_void>(
Arc::into_raw(buffer),
)
};
unsafe {
let _: () = msg_send![delegate, setFnPtr: fnptr_pinned];
let _: () = msg_send![delegate, setBufferPtr: buffer_as_ptr];
}
let queue =
@@ -684,6 +692,10 @@ impl AVCaptureVideoCallback {
pub fn inner(&self) -> *mut Object {
self.delegate
}
pub fn queue(&self) -> &NSObject {
&self.queue
}
}
impl Drop for AVCaptureVideoCallback {
@@ -840,7 +852,7 @@ impl AVCaptureDevice {
}
impl AVCaptureDevice {
pub fn new(index: CameraIndex) -> Result<Self, NokhwaError> {
pub fn new(index: &CameraIndex) -> Result<Self, NokhwaError> {
match &index {
CameraIndex::Index(idx) => {
let devices = AVCaptureDeviceDiscoverySession::new(
@@ -855,7 +867,10 @@ impl AVCaptureDevice {
.devices();
match devices.get(*idx as usize) {
Some(device) => Ok(AVCaptureDevice::from_id(&device.misc(), Some(index))?),
Some(device) => Ok(AVCaptureDevice::from_id(
&device.misc(),
Some(index.clone()),
)?),
None => Err(NokhwaError::OpenDeviceError(
idx.to_string(),
"Not Found".to_string(),
@@ -878,7 +893,7 @@ impl AVCaptureDevice {
));
}
let camera_info = get_raw_device_info(
index_hint.unwrap_or(CameraIndex::String(id.to_string())),
index_hint.unwrap_or_else(|| CameraIndex::String(id.to_string())),
capture,
);
Ok(AVCaptureDevice {
@@ -1069,18 +1084,10 @@ impl AVCaptureVideoDataOutput {
pub fn add_delegate(&self, delegate: &AVCaptureVideoCallback) -> Result<(), NokhwaError> {
unsafe {
let avf_queue_str = match CString::new("avf_queue") {
Ok(avf) => avf.into_raw(),
Err(_) => {
// should not happen
return Err(NokhwaError::GeneralError("String contains null? This is a bug, please report it: https://github.com/l1npengtul/nokhwa".to_string()));
}
};
let _: () = msg_send![
self.inner,
setSampleBufferDelegate: delegate.delegate
queue: avf_queue_str
queue: delegate.queue().0
];
};
Ok(())
+2
View File
@@ -1,5 +1,7 @@
//! Core type definitions for `nokhwa`
extern crate core;
pub mod buffer;
pub mod error;
pub mod pixel_format;
+9
View File
@@ -247,4 +247,13 @@ pub trait CaptureBackendTrait {
fn stop_stream(&mut self) -> Result<(), NokhwaError>;
}
impl<T> From<T> for Box<dyn CaptureBackendTrait>
where
T: CaptureBackendTrait + 'static,
{
fn from(capbackend: T) -> Self {
Box::new(capbackend)
}
}
pub trait VirtualBackendTrait {}
+6
View File
@@ -96,6 +96,12 @@ impl RequestedFormat {
}
}
impl Display for RequestedFormat {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
/// Describes the index of the camera.
/// - Index: A numbered index
/// - String: A string, used for IPCameras.
+37 -42
View File
@@ -14,28 +14,25 @@
* limitations under the License.
*/
use image::{Frame, ImageBuffer, Rgb};
use nokhwa_bindings_macos::{
AVCaptureDevice, AVCaptureDeviceInput, AVCaptureSession, AVCaptureVideoCallback,
AVCaptureVideoDataOutput,
};
use nokhwa_core::buffer::Buffer;
use nokhwa_core::{
buffer::Buffer,
error::NokhwaError,
traits::CaptureBackendTrait,
types::{
mjpeg_to_rgb, yuyv422_to_rgb, ApiBackend, CameraControl, CameraFormat, CameraIndex,
CameraInfo, ControlValueSetter, FrameFormat, KnownCameraControl, RequestedFormat,
Resolution,
ApiBackend, CameraControl, CameraFormat, CameraIndex, CameraInfo, ControlValueSetter,
FrameFormat, KnownCameraControl, RequestedFormat, Resolution,
},
};
use std::sync::LockResult;
use std::ffi::CString;
use std::{
borrow::Cow,
collections::HashMap,
sync::{Arc, Mutex},
};
use std::ffi::CString;
/// The backend struct that interfaces with V4L2.
/// To see what this does, please see [`CaptureBackendTrait`].
@@ -53,9 +50,9 @@ pub struct AVFoundationCaptureDevice {
data_out: Option<AVCaptureVideoDataOutput>,
data_collect: Option<AVCaptureVideoCallback>,
info: CameraInfo,
buffername: CString,
buffer_name: CString,
format: CameraFormat,
framebuf: Arc<Mutex<(Vec<u8>, FrameFormat)>>,
frame_buffer_lock: Arc<Mutex<(Vec<u8>, FrameFormat)>>,
}
impl AVFoundationCaptureDevice {
@@ -64,13 +61,22 @@ impl AVFoundationCaptureDevice {
/// If `camera_format` is `None`, it will be spawned with with 640x480@15 FPS, MJPEG [`CameraFormat`] default.
/// # Errors
/// This function will error if the camera is currently busy or if `AVFoundation` can't read device information, or permission was not given by the user.
pub fn new(index: CameraIndex, req_fmt: RequestedFormat) -> Result<Self, NokhwaError> {
pub fn new(index: &CameraIndex, req_fmt: RequestedFormat) -> Result<Self, NokhwaError> {
let mut device = AVCaptureDevice::new(index)?;
device.lock()?;
let formats = device.supported_formats()?;
let camera_fmt = req_fmt.fulfill(&formats)?;
let camera_fmt = req_fmt.fulfill(&formats).ok_or_else(|| {
NokhwaError::OpenDeviceError("Cannot fulfill request".to_string(), req_fmt.to_string())
})?;
device.set_all(camera_fmt)?;
let device_descriptor = device.
let device_descriptor = device.info().clone();
let buffername =
CString::new(format!("{}_INDEX{}_", device_descriptor, index)).map_err(|why| {
NokhwaError::StructureError {
structure: "CString Buffername".to_string(),
error: why.to_string(),
}
})?;
Ok(AVFoundationCaptureDevice {
device,
@@ -78,10 +84,10 @@ impl AVFoundationCaptureDevice {
session: None,
data_out: None,
data_collect: None,
info: ,
buffername: ,
format: camera_format,
framebuf: Arc::new(Mutex::new((vec![], FrameFormat::MJPEG))),
info: device_descriptor,
buffer_name: buffername,
format: camera_fmt,
frame_buffer_lock: Arc::new(Mutex::new((vec![], FrameFormat::MJPEG))),
})
}
@@ -90,6 +96,7 @@ impl AVFoundationCaptureDevice {
/// # Errors
/// This function will error if the camera is currently busy or if `AVFoundation` can't read device information, or permission was not given by the user.
#[deprecated(since = "0.10.0", note = "please use `new` instead.")]
#[allow(clippy::cast_possible_truncation)]
pub fn new_with(
index: usize,
width: u32,
@@ -99,7 +106,7 @@ impl AVFoundationCaptureDevice {
) -> Result<Self, NokhwaError> {
let camera_format = CameraFormat::new_from(width, height, fourcc, fps);
AVFoundationCaptureDevice::new(
CameraIndex::Index(index as u32),
&CameraIndex::Index(index as u32),
RequestedFormat::Exact(camera_format),
)
}
@@ -123,7 +130,7 @@ impl CaptureBackendTrait for AVFoundationCaptureDevice {
}
fn set_camera_format(&mut self, new_fmt: CameraFormat) -> Result<(), NokhwaError> {
self.device.set_all(new_fmt.into())?;
self.device.set_all(new_fmt)?;
self.format = new_fmt;
Ok(())
}
@@ -144,7 +151,7 @@ impl CaptureBackendTrait for AVFoundationCaptureDevice {
match res_list.get_mut(&format.resolution()) {
Some(fpses) => Vec::push(fpses, format.frame_rate()),
None => {
vec![format.frame_rate()]
res_list.insert(format.resolution(), vec![format.frame_rate()]);
}
}
}
@@ -156,7 +163,7 @@ impl CaptureBackendTrait for AVFoundationCaptureDevice {
.device
.supported_formats()?
.into_iter()
.map(|fmt| FrameFormat::from(fmt.fourcc))
.map(|fmt| fmt.format())
.collect::<Vec<FrameFormat>>();
formats.sort();
formats.dedup();
@@ -221,18 +228,11 @@ impl CaptureBackendTrait for AVFoundationCaptureDevice {
session.begin_configuration();
session.add_input(&input)?;
let mut frame_mutex = self.framebuf.clone();
let func_callback: Box<dyn FnMut(Vec<u8>, FrameFormat) + Send + Sync + 'static> =
Box::new(|data: Vec<u8>, frame_format: FrameFormat| {
if let Ok(lck) = frame_mutex.lock() {
*lck = (data, frame_format);
}
});
let videocallback = AVCaptureVideoCallback::new(func_callback)?;
let frame_mutex = self.frame_buffer_lock.clone();
let bufname = &self.buffer_name;
let videocallback = AVCaptureVideoCallback::new(bufname, frame_mutex)?;
let output = AVCaptureVideoDataOutput::new();
output.add_delegate(&callback)?;
output.add_delegate(&videocallback)?;
session.add_output(&output)?;
session.commit_configuration();
session.start()?;
@@ -266,19 +266,14 @@ impl CaptureBackendTrait for AVFoundationCaptureDevice {
}
fn frame_raw(&mut self) -> Result<Cow<[u8]>, NokhwaError> {
let mut framebuffer_empty = match self.framebuf.lock() {
Ok(f) => {
if f.0.is_empty() {
true
}
false
}
let mut framebuffer_empty = match self.frame_buffer_lock.lock() {
Ok(f) => f.0.is_empty(),
Err(why) => return Err(NokhwaError::ReadFrameError(why.to_string())),
};
loop {
if framebuffer_empty {
match self.framebuf.lock() {
match self.frame_buffer_lock.lock() {
Ok(f) => framebuffer_empty = f.0.is_empty(),
Err(why) => return Err(NokhwaError::ReadFrameError(why.to_string())),
}
@@ -287,13 +282,13 @@ impl CaptureBackendTrait for AVFoundationCaptureDevice {
}
}
match self.framebuf.lock() {
Ok(f) => {
match self.frame_buffer_lock.lock() {
Ok(mut f) => {
let mut new_frame = vec![];
std::mem::swap(&mut new_frame, &mut f.0);
Ok(Cow::from(new_frame))
}
Err(why) => return Err(NokhwaError::ReadFrameError(why.to_string())),
Err(why) => Err(NokhwaError::ReadFrameError(why.to_string())),
}
}
+8 -11
View File
@@ -13,21 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use image::{ImageBuffer, Rgb};
use nokhwa_bindings_windows::wmf::MediaFoundationDevice;
use nokhwa_core::buffer::Buffer;
use nokhwa_core::types::ControlValueSetter;
use nokhwa_core::{
buffer::Buffer,
error::NokhwaError,
traits::CaptureBackendTrait,
types::{
all_known_camera_controls, mjpeg_to_rgb, yuyv422_to_rgb, ApiBackend, CameraControl,
CameraFormat, CameraIndex, CameraInfo, FrameFormat, KnownCameraControl,
KnownCameraControlFlag, RequestedFormat, Resolution,
all_known_camera_controls, ApiBackend, CameraControl, CameraFormat, CameraIndex,
CameraInfo, ControlValueSetter, FrameFormat, KnownCameraControl, RequestedFormat,
Resolution,
},
};
use std::{any::Any, borrow::Cow, collections::HashMap};
use std::{borrow::Cow, collections::HashMap};
/// The backend that deals with Media Foundation on Windows.
/// To see what this does, please see [`CaptureBackendTrait`].
@@ -50,14 +47,14 @@ impl<'a> MediaFoundationCaptureDevice<'a> {
/// Creates a new capture device using the Media Foundation backend. Indexes are gives to devices by the OS, and usually numbered by order of discovery.
/// # Errors
/// This function will error if Media Foundation fails to get the device.
pub fn new(index: CameraIndex, camera_fmt: RequestedFormat) -> Result<Self, NokhwaError> {
pub fn new(index: &CameraIndex, camera_fmt: RequestedFormat) -> Result<Self, NokhwaError> {
let mut mf_device = MediaFoundationDevice::new(index.clone())?;
let info = CameraInfo::new(
&mf_device.name(),
&"MediaFoundation Camera Device".to_string(),
&mf_device.symlink(),
index,
index.clone(),
);
let availible = mf_device
@@ -91,7 +88,7 @@ impl<'a> MediaFoundationCaptureDevice<'a> {
/// This function will error if Media Foundation fails to get the device.
#[deprecated(since = "0.10", note = "please use `new` instead.")]
pub fn new_with(
index: CameraIndex,
index: &CameraIndex,
width: u32,
height: u32,
fps: u32,
+2 -2
View File
@@ -100,7 +100,7 @@ impl OpenCvCaptureDevice {
/// If the backend fails to open the camera (e.g. Device does not exist at specified index/ip), Camera does not support specified [`CameraFormat`], and/or other `OpenCV` Error, this will error.
/// # Panics
/// If the API u32 -> i32 fails this will error
pub fn new(index: CameraIndex, cfmt: RequestedFormat) -> Result<Self, NokhwaError> {
pub fn new(index: &CameraIndex, cfmt: RequestedFormat) -> Result<Self, NokhwaError> {
let api_pref = if index.is_string() {
CAP_ANY
} else {
@@ -132,7 +132,7 @@ impl OpenCvCaptureDevice {
Ok(OpenCvCaptureDevice {
camera_format,
camera_location: index,
camera_location: index.clone(),
camera_info,
api_preference: api,
video_capture,
+1 -2
View File
@@ -19,7 +19,6 @@ use nokhwa_core::types::{CameraFormat, CameraInfo};
use nokhwa_core::{
buffer::Buffer,
error::NokhwaError,
pixel_format::FormatDecoder,
traits::CaptureBackendTrait,
types::{
mjpeg_to_rgb, yuyv422_to_rgb, ApiBackend, CameraControl, CameraIndex,
@@ -116,7 +115,7 @@ impl<'a> V4LCaptureDevice<'a> {
/// If `camera_format` is not `None`, the camera will try to use it when you call [`init()`](crate::CaptureBackendTrait::init).
/// # Errors
/// This function will error if the camera is currently busy or if `V4L2` can't read device information.
pub fn new(index: CameraIndex, cam_fmt: RequestedFormat) -> Result<Self, NokhwaError> {
pub fn new(index: &CameraIndex, cam_fmt: RequestedFormat) -> Result<Self, NokhwaError> {
let mut device = match Device::new(index.as_index()? as usize) {
Ok(dev) => dev,
Err(why) => {
+1 -1
View File
@@ -437,7 +437,7 @@ macro_rules! cap_impl_fn {
fn [< init_ $backend_name>](idx: &CameraIndex, setting: RequestedFormat) -> Option<Result<Box<dyn CaptureBackendTrait>, NokhwaError>> {
use crate::backends::capture::$backend;
match <$backend>::$init_fn(idx, setting) {
Ok(cap) => Some(Ok(cp.into())),
Ok(cap) => Some(Ok(cap.into())),
Err(why) => Some(Err(why)),
}
}
+5 -6
View File
@@ -26,10 +26,11 @@ fn init_avfoundation(callback: impl Fn(bool) + Send + 'static) {
feature = "input-avfoundation",
any(target_os = "macos", target_os = "ios")
))]
fn init_avfoundation(callback: impl Fn(bool) + Send + 'static) {
fn init_avfoundation(callback: impl Fn(bool) + Send + Sync + 'static) {
use nokhwa_bindings_macos::request_permission_with_callback;
request_permission_with_callback(callback);
let boxed: Box<dyn Fn(bool) + Send + Sync + 'static> = Box::new(callback);
request_permission_with_callback(boxed);
}
#[cfg(not(all(
@@ -45,9 +46,7 @@ fn status_avfoundation() -> bool {
any(target_os = "macos", target_os = "ios")
))]
fn status_avfoundation() -> bool {
use nokhwa_bindings_macos::avfoundation::{
current_authorization_status, AVAuthorizationStatus,
};
use nokhwa_bindings_macos::{current_authorization_status, AVAuthorizationStatus};
matches!(
current_authorization_status(),
@@ -60,7 +59,7 @@ fn status_avfoundation() -> bool {
///
/// The `on_complete` is called after initialization (a.k.a User granted permission). The callback's argument
/// is weather the initialization was successful or not
pub fn nokhwa_initialize(on_complete: impl Fn(bool) + Send + 'static) {
pub fn nokhwa_initialize(on_complete: impl Fn(bool) + Send + Sync + 'static) {
init_avfoundation(on_complete);
}
+2 -3
View File
@@ -298,11 +298,10 @@ fn query_msmf() -> Result<Vec<CameraInfo>, NokhwaError> {
any(target_os = "macos", target_os = "ios")
))]
fn query_avfoundation() -> Result<Vec<CameraInfo>, NokhwaError> {
use nokhwa_bindings_macos::avfoundation::query_avfoundation as q_avf;
use nokhwa_bindings_macos::query_avfoundation;
Ok(q_avf()?
Ok(query_avfoundation()?
.into_iter()
.map(CameraInfo::from)
.collect::<Vec<CameraInfo>>())
}