diff --git a/Examples/OpenSeeWebcamInfo.cs b/Examples/OpenSeeWebcamInfo.cs index ecdf366..237c792 100644 --- a/Examples/OpenSeeWebcamInfo.cs +++ b/Examples/OpenSeeWebcamInfo.cs @@ -63,14 +63,14 @@ public class OpenSeeWebcam { private List> splitCaps = null; private int CompareCaps(OpenSeeWebcamCapability a, OpenSeeWebcamCapability b) { - if (a.rating < b.rating) - return -1; - if (a.rating > b.rating) - return 1; if (a.minCX * a.minCY > b.minCX * b.minCY) return -1; if (a.minCX * a.minCY < b.minCX * b.minCY) return 1; + if (a.rating < b.rating) + return -1; + if (a.rating > b.rating) + return 1; if (a.minInterval < b.minInterval) return -1; if (a.minInterval > b.minInterval) @@ -124,6 +124,7 @@ public class OpenSeeWebcam { OpenSeeWebcamCapability cap = new OpenSeeWebcamCapability(); cap.id = -1; cap.format = OpenSeeWebcamFormat.Any; + cap.minInterval = 10000000 / 60; return cap; } dshowOffset = 1; @@ -204,6 +205,7 @@ public class OpenSeeWebcamInfo : MonoBehaviour { int length = bm_get_json_length_x64(); StringBuilder buffer = new StringBuilder(length); bm_get_json_x64(buffer, length); + //return "[{\"id\": 0,\"name\": \"DeckLink Mini Recorder\",\"bmId\": 2592763504,\"bmFlags\": \"1\",\"caps\": [{\"id\": 0,\"minCX\": 720,\"minCY\": 486,\"maxCX\": 720,\"maxCY\": 486,\"granularityCX\": 1,\"granularityCY\": 1,\"minInterval\": 166833,\"maxInterval\": 166833,\"bmTimescale\": 60000,\"bmFrameduration\": 1001,\"bmModecode\": 1853125488,\"rating\": 1,\"format\": 100},{\"id\": 1,\"minCX\": 720,\"minCY\": 576,\"maxCX\": 720,\"maxCY\": 576,\"granularityCX\": 1,\"granularityCY\": 1,\"minInterval\": 200000,\"maxInterval\": 200000,\"bmTimescale\": 50000,\"bmFrameduration\": 1000,\"bmModecode\": 1885432944,\"rating\": 1,\"format\": 100},{\"id\": 2,\"minCX\": 1920,\"minCY\": 1080,\"maxCX\": 1920,\"maxCY\": 1080,\"granularityCX\": 1,\"granularityCY\": 1,\"minInterval\": 417083,\"maxInterval\": 417083,\"bmTimescale\": 24000,\"bmFrameduration\": 1001,\"bmModecode\": 842231923,\"rating\": 1,\"format\": 100},{\"id\": 3,\"minCX\": 1920,\"minCY\": 1080,\"maxCX\": 1920,\"maxCY\": 1080,\"granularityCX\": 1,\"granularityCY\": 1,\"minInterval\": 416666,\"maxInterval\": 416666,\"bmTimescale\": 24000,\"bmFrameduration\": 1000,\"bmModecode\": 842297459,\"rating\": 1,\"format\": 100},{\"id\": 4,\"minCX\": 1920,\"minCY\": 1080,\"maxCX\": 1920,\"maxCY\": 1080,\"granularityCX\": 1,\"granularityCY\": 1,\"minInterval\": 400000,\"maxInterval\": 400000,\"bmTimescale\": 25000,\"bmFrameduration\": 1000,\"bmModecode\": 1215312437,\"rating\": 1,\"format\": 100},{\"id\": 5,\"minCX\": 1920,\"minCY\": 1080,\"maxCX\": 1920,\"maxCY\": 1080,\"granularityCX\": 1,\"granularityCY\": 1,\"minInterval\": 333666,\"maxInterval\": 333666,\"bmTimescale\": 30000,\"bmFrameduration\": 1001,\"bmModecode\": 1215312441,\"rating\": 1,\"format\": 100},{\"id\": 6,\"minCX\": 1920,\"minCY\": 1080,\"maxCX\": 1920,\"maxCY\": 1080,\"granularityCX\": 1,\"granularityCY\": 1,\"minInterval\": 333333,\"maxInterval\": 333333,\"bmTimescale\": 30000,\"bmFrameduration\": 1000,\"bmModecode\": 1215312688,\"rating\": 1,\"format\": 100},{\"id\": 7,\"minCX\": 1280,\"minCY\": 720,\"maxCX\": 1280,\"maxCY\": 720,\"granularityCX\": 1,\"granularityCY\": 1,\"minInterval\": 200000,\"maxInterval\": 200000,\"bmTimescale\": 50000,\"bmFrameduration\": 1000,\"bmModecode\": 1752184112,\"rating\": 1,\"format\": 100},{\"id\": 8,\"minCX\": 1280,\"minCY\": 720,\"maxCX\": 1280,\"maxCY\": 720,\"granularityCX\": 1,\"granularityCY\": 1,\"minInterval\": 166833,\"maxInterval\": 166833,\"bmTimescale\": 60000,\"bmFrameduration\": 1001,\"bmModecode\": 1752184121,\"rating\": 1,\"format\": 100},{\"id\": 9,\"minCX\": 1280,\"minCY\": 720,\"maxCX\": 1280,\"maxCY\": 720,\"granularityCX\": 1,\"granularityCY\": 1,\"minInterval\": 166666,\"maxInterval\": 166666,\"bmTimescale\": 60000,\"bmFrameduration\": 1000,\"bmModecode\": 1752184368,\"rating\": 1,\"format\": 100}]}]"; return buffer.ToString(); } diff --git a/Unity/Plugins/libminibmcapture32.dll b/Unity/Plugins/libminibmcapture32.dll index 34d9c93..dc409da 100644 Binary files a/Unity/Plugins/libminibmcapture32.dll and b/Unity/Plugins/libminibmcapture32.dll differ diff --git a/Unity/Plugins/libminibmcapture64.dll b/Unity/Plugins/libminibmcapture64.dll index 9b7d1de..6583947 100644 Binary files a/Unity/Plugins/libminibmcapture64.dll and b/Unity/Plugins/libminibmcapture64.dll differ diff --git a/dshowcapture.py b/dshowcapture.py index cfb4e79..e1c5293 100644 --- a/dshowcapture.py +++ b/dshowcapture.py @@ -12,22 +12,30 @@ def resolve(name): return f lib = None +bm_lib = None -def create_frame_buffer(width, height): - buffer = bytearray(width * height * 4 * 4) +def create_frame_buffer(width, height, factor): + buffer = bytearray(width * height * 4 * factor) char_array = c_char * len(buffer) return char_array.from_buffer(buffer) class DShowCapture(): def __init__(self): global lib - if lib is None: + global bm_lib + if lib is None or bm_lib is None: if platform.architecture()[0] == '32bit': dll_path = resolve(os.path.join("dshowcapture", "dshowcapture_x86.dll")) lib = cdll.LoadLibrary(dll_path) + dll_path = resolve(os.path.join("dshowcapture", "libminibmcapture32.dll")) + bm_lib = cdll.LoadLibrary(dll_path) else: dll_path = resolve(os.path.join("dshowcapture", "dshowcapture_x64.dll")) lib = cdll.LoadLibrary(dll_path) + dll_path = resolve(os.path.join("dshowcapture", "libminibmcapture64.dll")) + bm_lib = cdll.LoadLibrary(dll_path) + + # DirectShow lib.create_capture.restype = c_void_p lib.get_devices.argtypes = [c_void_p] lib.get_device.argtypes = [c_void_p, c_int, c_char_p, c_int] @@ -47,7 +55,15 @@ class DShowCapture(): lib.get_json_length.argtypes = [c_void_p] lib.get_json.argtypes = [c_void_p, c_char_p, c_int] lib.destroy_capture.argtypes = [c_void_p] + + # Blackmagic + bm_lib.start_capture_single.argtypes = [c_int, c_int, c_void_p] + bm_lib.read_frame_bgra32_blocking.argtypes = [c_char_p, c_int] + bm_lib.stop_capture_single.argtypes = [] + bm_lib.get_json_length.argtypes = [] + bm_lib.get_json.argtypes = [c_char_p, c_int] self.lib = lib + self.bm_lib = bm_lib self.cap = lib.create_capture() self.name_buffer = create_string_buffer(255); self.buffer = None @@ -55,6 +71,7 @@ class DShowCapture(): self.size = None self.real_size = None self.info = None + self.type = None def __del__(self): if not self.buffer is None: @@ -72,11 +89,27 @@ class DShowCapture(): return name_str def get_info(self): + # DirectShow json_length = self.lib.get_json_length(self.cap); json_buffer = create_string_buffer(json_length) self.lib.get_json(self.cap, json_buffer, json_length); json_text = str(json_buffer.value.decode('utf8')) self.info = json.loads(json_text) + for cam in self.info: + cam["type"] = "DirectShow" + + # Blackmagic + json_length = self.bm_lib.get_json_length(); + json_buffer = create_string_buffer(json_length) + self.bm_lib.get_json(json_buffer, json_length); + json_text = str(json_buffer.value.decode('utf8')) + bm_info = json.loads(json_text) + dshow_len = len(self.info) + for cam in bm_info: + cam["type"] = "Blackmagic" + cam["id"] += dshow_len + self.info.extend(bm_info) + return self.info def capture_device(self, cam, width, height, fps): @@ -84,12 +117,15 @@ class DShowCapture(): self.get_devices() ret = self.lib.capture_device(self.cap, cam, width, height, fps) == 1 if ret: + self.type = "DirectShow" self.width = self.get_width() self.height = self.get_height() + self.fps = self.get_fps() self.flipped = self.get_flipped() self.colorspace = self.get_colorspace() + self.colorspace_internal = self.get_colorspace_internal() self.size = self.width * self.height * 4 - self.buffer = create_frame_buffer(self.width, self.height) + self.buffer = create_frame_buffer(self.width, self.height, 4) else: self.size = None self.real_size = None @@ -98,15 +134,35 @@ class DShowCapture(): def capture_device_by_dcap(self, cam, dcap, width, height, fps): if not self.have_devices: self.get_devices() + if self.info is None: + self.get_info() fps = int(10000000 / fps) - ret = self.lib.capture_device_by_dcap(self.cap, cam, dcap, width, height, fps) == 1 + ret = False + if self.info[cam]['type'] == "DirectShow": + ret = self.lib.capture_device_by_dcap(self.cap, cam, dcap, width, height, fps) == 1 + elif self.info[cam]['type'] == "Blackmagic": + ret = self.bm_lib.start_capture_single(self.info[cam]['bmId'], self.info[cam]['caps'][dcap]['bmModecode'], None) != 0 if ret: - self.width = self.get_width() - self.height = self.get_height() - self.flipped = self.get_flipped() - self.colorspace = self.get_colorspace() - self.size = self.width * self.height * 4 * 4 - self.buffer = create_frame_buffer(self.width, self.height) + self.type = self.info[cam]['type'] + if self.type == "DirectShow": + self.width = self.get_width() + self.height = self.get_height() + self.fps = self.get_fps() + self.flipped = self.get_flipped() + self.colorspace = self.get_colorspace() + self.colorspace_internal = self.get_colorspace_internal() + self.size = self.width * self.height * 4 * 4 + self.buffer = create_frame_buffer(self.width, self.height, 4) + elif self.type == "Blackmagic": + self.width = self.info[cam]['caps'][dcap]['minCX'] + self.height = self.info[cam]['caps'][dcap]['minCY'] + self.fps = int(10000000 / self.info[cam]['caps'][dcap]['minInterval']) + self.flipped = False + self.colorspace = 101 + self.colorspace_internal = self.colorspace + self.size = self.width * self.height * 4 + self.buffer = create_frame_buffer(self.width, self.height, 1) + self.real_size = self.size else: self.size = None self.real_size = None @@ -117,12 +173,15 @@ class DShowCapture(): self.get_devices() ret = self.lib.capture_device_default(self.cap, cam) == 1 if ret: + self.type = "DirectShow" self.width = self.get_width() self.height = self.get_height() + self.fps = self.get_fps() self.flipped = self.get_flipped() self.colorspace = self.get_colorspace() + self.colorspace_internal = self.get_colorspace_internal() self.size = self.width * self.height * 4 * 4 - self.buffer = create_frame_buffer(self.width, self.height) + self.buffer = create_frame_buffer(self.width, self.height, 4) else: self.size = None self.real_size = None @@ -155,7 +214,12 @@ class DShowCapture(): def get_frame(self, timeout): if self.size is None: return None - self.real_size = self.lib.get_frame(self.cap, timeout, self.buffer, self.size) + if self.type == "DirectShow": + self.real_size = self.lib.get_frame(self.cap, timeout, self.buffer, self.size) + elif self.type == "Blackmagic": + self.bm_lib.read_frame_bgra32_blocking(self.buffer, self.size) + else: + return None img = np.frombuffer(self.buffer, dtype=np.uint8)[0:self.real_size] if self.colorspace in [100, 101]: if self.real_size == self.height * self.width * 4: @@ -192,11 +256,16 @@ class DShowCapture(): def stop_capture(self): self.size = None self.real_size = None - return self.lib.stop_capture(self.cap) + if self.type == "DirectShow": + return self.lib.stop_capture(self.cap) + elif self.type == "Blackmagic": + return self.bm_lib.stop_capture_single() != 0 + return True def destroy_capture(self): if self.cap is None: return 0 + self.stop_capture() ret = self.lib.destroy_capture(self.cap) self.cap = None self.size = None diff --git a/dshowcapture/libminibmcapture32.dll b/dshowcapture/libminibmcapture32.dll index 34d9c93..dc409da 100644 Binary files a/dshowcapture/libminibmcapture32.dll and b/dshowcapture/libminibmcapture32.dll differ diff --git a/dshowcapture/libminibmcapture64.dll b/dshowcapture/libminibmcapture64.dll index 9b7d1de..6583947 100644 Binary files a/dshowcapture/libminibmcapture64.dll and b/dshowcapture/libminibmcapture64.dll differ diff --git a/facetracker.py b/facetracker.py index ed4c7e4..6ee0636 100644 --- a/facetracker.py +++ b/facetracker.py @@ -78,7 +78,10 @@ if os.name == 'nt' and (args.list_cameras > 0 or not args.list_dcaps is None): formats = {0: "Any", 1: "Unknown", 100: "ARGB", 101: "XRGB", 200: "I420", 201: "NV12", 202: "YV12", 203: "Y800", 300: "YVYU", 301: "YUY2", 302: "UYVY", 303: "HDYC (Unsupported)", 400: "MJPEG", 401: "H264" } for cam in info: if args.list_dcaps == -1: - print(f"{cam['id']}: {cam['name']}") + type = "" + if cam['type'] == "Blackmagic": + type = "Blackmagic: " + print(f"{cam['id']}: {type}{cam['name']}") if args.list_dcaps != -1 and args.list_dcaps != cam['id']: continue for caps in cam['caps']: diff --git a/facetracker.spec b/facetracker.spec index 1df151f..060d850 100644 --- a/facetracker.spec +++ b/facetracker.spec @@ -5,7 +5,7 @@ block_cipher = None a = Analysis(['facetracker.py'], pathex=['C:\\OpenSeeFaceBuild'], - binaries=[('dshowcapture/dshowcapture_x86.dll', '.'), ('dshowcapture/dshowcapture_x64.dll', '.'), ('escapi/escapi_x86.dll', '.'), ('escapi/escapi_x64.dll', '.'), ('run.bat', '.'), ('msvcp140.dll', '.'), ('vcomp140.dll', '.'), ('concrt140.dll', '.'), ('vccorlib140.dll', '.')], + binaries=[('dshowcapture/dshowcapture_x86.dll', '.'), ('dshowcapture/dshowcapture_x64.dll', '.'), ('dshowcapture/libminibmcapture32.dll', '.'), ('dshowcapture/libminibmcapture64.dll', '.'), ('escapi/escapi_x86.dll', '.'), ('escapi/escapi_x64.dll', '.'), ('run.bat', '.'), ('msvcp140.dll', '.'), ('vcomp140.dll', '.'), ('concrt140.dll', '.'), ('vccorlib140.dll', '.')], datas=[], hiddenimports=[], hookspath=[], diff --git a/input_reader.py b/input_reader.py index 709308d..30e7bb6 100644 --- a/input_reader.py +++ b/input_reader.py @@ -62,18 +62,26 @@ class DShowCaptureReader(VideoReader): self.dcap = dcap; self.device = dshowcapture.DShowCapture() self.device.get_devices() - self.name = self.device.get_device(capture) + info = self.device.get_info() + self.name = info[capture]['name'] + if info[capture]['type'] == "Blackmagic": + self.name = "Blackmagic: " + self.name + if dcap is None or dcap < 0: + dcap = 0 + ret = False if dcap is None: - print("Opening classic") - self.device.capture_device(capture, self.width, self.height, self.fps) + ret = self.device.capture_device(capture, self.width, self.height, self.fps) else: if dcap < 0: - print("Opening default") - self.device.capture_device_default(capture) + ret = self.device.capture_device_default(capture) else: - print("Opening exact") - self.device.capture_device_by_dcap(capture, dcap, self.width, self.height, self.fps) - print(f"Camera: \"{self.name}\" Capability ID: {dcap} Resolution: {self.device.get_width()}x{self.device.get_height()} Frame rate: {self.device.get_fps()} Colorspace: {self.device.get_colorspace()} Internal: {self.device.get_colorspace_internal()} Flipped: {self.device.get_flipped()}") + ret = self.device.capture_device_by_dcap(capture, dcap, self.width, self.height, self.fps) + if not ret: + raise Exception("Failed to start capture.") + self.width = self.device.width + self.height = self.device.height + self.fps = self.device.fps + print(f"Camera: \"{self.name}\" Capability ID: {dcap} Resolution: {self.device.width}x{self.device.height} Frame rate: {self.device.fps} Colorspace: {self.device.colorspace} Internal: {self.device.colorspace_internal} Flipped: {self.device.flipped}") self.timeout = 1000 def is_open(self): return self.device.capturing() diff --git a/make_exe.bat b/make_exe.bat index 8e436ae..a5ad490 100644 --- a/make_exe.bat +++ b/make_exe.bat @@ -1,17 +1,8 @@ -REM pyinstaller facetracker.spec --onefile ^ -REM --add-binary dshowcapture/dshowcapture_x86.dll;. ^ -REM --add-binary dshowcapture/dshowcapture_x64.dll;. ^ -REM --add-binary escapi/escapi_x86.dll;. ^ -REM --add-binary escapi/escapi_x64.dll;. ^ -REM --add-binary msvcp140.dll;. ^ -REM --add-binary vcomp140.dll;. ^ -REM --add-binary concrt140.dll;. ^ -REM --add-binary vccorlib140.dll;. ^ -REM --add-binary run.bat;. - pyinstaller facetracker.spec --onedir ^ --add-binary dshowcapture/dshowcapture_x86.dll;. ^ --add-binary dshowcapture/dshowcapture_x64.dll;. ^ + --add-binary dshowcapture/libminibmcapture32.dll;. ^ + --add-binary dshowcapture/libminibmcapture64.dll;. ^ --add-binary escapi/escapi_x86.dll;. ^ --add-binary escapi/escapi_x64.dll;. ^ --add-binary msvcp140.dll;. ^