Rearrage include and src

This commit is contained in:
John Zhao
2018-10-27 21:24:04 +08:00
parent 08271be063
commit 972ab79a76
72 changed files with 280 additions and 260 deletions

10
src/mynteye/uvc/README.md Normal file
View File

@@ -0,0 +1,10 @@
## `uvc-v4l2.cc`
* [Linux Media Subsystem Documentation](https://www.kernel.org/doc/html/latest/media/index.html)
* [The Linux USB Video Class (UVC) driver](https://www.kernel.org/doc/html/latest/media/v4l-drivers/uvcvideo.html)
## `uvc-wmf.cc`
* [Media Foundation](https://msdn.microsoft.com/en-us/library/ms694197(VS.85).aspx)
* [USB Video Class Driver](https://docs.microsoft.com/en-us/windows-hardware/drivers/stream/usb-video-class-driver)

View File

@@ -0,0 +1,200 @@
// Copyright 2018 Slightech Co., Ltd. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "mynteye/uvc/uvc.h"
#include <libuvc/libuvc.h>
#include "mynteye/logger.h"
// #define ENABLE_DEBUG_SPAM
MYNTEYE_BEGIN_NAMESPACE
namespace uvc {
static void check(const char *call, uvc_error_t status) {
LOG_IF(FATAL, status < 0)
<< call << "(...) returned " << uvc_strerror(status);
}
#define CALL_UVC(name, ...) check(#name, name(__VA_ARGS__))
struct context {
uvc_context_t *ctx;
context() : ctx(nullptr) {
VLOG(2) << __func__;
CALL_UVC(uvc_init, &ctx, nullptr);
}
~context() {
VLOG(2) << __func__;
if (ctx)
uvc_exit(ctx);
}
};
struct device {
const std::shared_ptr<context> parent;
uvc_device_t *uvcdevice = nullptr;
uvc_device_handle_t *handle = nullptr;
int vid, pid;
device(std::shared_ptr<context> parent, uvc_device_t *uvcdevice)
: parent(parent), uvcdevice(uvcdevice) {
VLOG(2) << __func__;
open();
uvc_device_descriptor_t *desc;
CALL_UVC(uvc_get_device_descriptor, uvcdevice, &desc);
vid = desc->idVendor;
pid = desc->idProduct;
uvc_free_device_descriptor(desc);
}
~device() {
VLOG(2) << __func__;
if (handle)
uvc_close(handle);
if (uvcdevice)
uvc_unref_device(uvcdevice);
}
void open() {
if (!handle)
CALL_UVC(uvc_open, uvcdevice, &handle);
}
};
std::shared_ptr<context> create_context() {
return std::make_shared<context>();
}
std::vector<std::shared_ptr<device>> query_devices(
std::shared_ptr<context> context) {
std::vector<std::shared_ptr<device>> devices;
uvc_device_t **list;
CALL_UVC(uvc_get_device_list, context->ctx, &list);
for (auto it = list; *it; ++it) {
try {
auto dev = std::make_shared<device>(context, *it);
devices.push_back(dev);
} catch (std::runtime_error &e) {
LOG(WARNING) << "usb:" << static_cast<int>(uvc_get_bus_number(*it)) << ':'
<< static_cast<int>(uvc_get_device_address(*it)) << ": "
<< e.what();
}
}
uvc_free_device_list(list, 1);
return devices;
}
int get_vendor_id(const device &device) {
return device.vid;
}
int get_product_id(const device &device) {
return device.pid;
}
std::string get_name(const device &device) {
// TODO(JohnZhao)
MYNTEYE_UNUSED(device)
return "";
}
std::string get_video_name(const device &device) {
// TODO(JohnZhao)
MYNTEYE_UNUSED(device)
return "";
}
bool pu_control_range(
const device &device, Option option, int32_t *min, int32_t *max,
int32_t *def) {
// TODO(JohnZhao)
MYNTEYE_UNUSED(device)
MYNTEYE_UNUSED(option)
MYNTEYE_UNUSED(min)
MYNTEYE_UNUSED(max)
MYNTEYE_UNUSED(def)
return false;
}
bool pu_control_query(
const device &device, Option option, pu_query query, int32_t *value) {
// TODO(JohnZhao)
MYNTEYE_UNUSED(device)
MYNTEYE_UNUSED(option)
MYNTEYE_UNUSED(query)
MYNTEYE_UNUSED(value)
return false;
}
bool xu_control_range(
const device &device, const xu &xu, uint8_t selector, uint8_t id, int32_t *min,
int32_t *max, int32_t *def) {
// TODO(JohnZhao)
MYNTEYE_UNUSED(device)
MYNTEYE_UNUSED(xu)
MYNTEYE_UNUSED(selector)
MYNTEYE_UNUSED(id)
MYNTEYE_UNUSED(min)
MYNTEYE_UNUSED(max)
MYNTEYE_UNUSED(def)
return false;
}
bool xu_control_query(
const device &device, const xu &xu, uint8_t selector, xu_query query,
uint16_t size, uint8_t *data) {
// TODO(JohnZhao)
MYNTEYE_UNUSED(device)
MYNTEYE_UNUSED(xu)
MYNTEYE_UNUSED(selector)
MYNTEYE_UNUSED(query)
MYNTEYE_UNUSED(size)
MYNTEYE_UNUSED(data)
return false;
}
void set_device_mode(
device &device, int width, int height, int fourcc, int fps, // NOLINT
video_channel_callback callback) {
// TODO(JohnZhao)
MYNTEYE_UNUSED(device)
MYNTEYE_UNUSED(width)
MYNTEYE_UNUSED(height)
MYNTEYE_UNUSED(fourcc)
MYNTEYE_UNUSED(fps)
MYNTEYE_UNUSED(callback)
}
void start_streaming(device &device, int num_transfer_bufs) { // NOLINT
// TODO(JohnZhao)
MYNTEYE_UNUSED(device)
MYNTEYE_UNUSED(num_transfer_bufs)
}
void stop_streaming(device &device) { // NOLINT
// TODO(JohnZhao)
MYNTEYE_UNUSED(device)
}
} // namespace uvc
MYNTEYE_END_NAMESPACE

603
src/mynteye/uvc/uvc-v4l2.cc Executable file
View File

@@ -0,0 +1,603 @@
// Copyright 2018 Slightech Co., Ltd. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "mynteye/uvc/uvc.h"
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <linux/usb/video.h>
#include <linux/uvcvideo.h>
#include <linux/videodev2.h>
#include <chrono>
#include <fstream>
#include <sstream>
#include <string>
#include <thread>
#include "mynteye/logger.h"
MYNTEYE_BEGIN_NAMESPACE
namespace uvc {
#define LOG_ERROR(severity, str) \
do { \
LOG(severity) << str << " error " << errno << ", " << strerror(errno); \
} while (0)
#define NO_DATA_MAX_COUNT 200
int no_data_count = 0;
/*
class device_error : public std::exception {
public:
explicit device_error(const std::string &what_arg) noexcept
: what_message_(std::move(what_arg)) {}
explicit device_error(const char *what_arg) noexcept
: what_message_(std::move(what_arg)) {}
const char *what() const noexcept {
return what_message_.c_str();
}
private:
std::string what_message_;
};
*/
struct throw_error {
throw_error() = default;
explicit throw_error(const std::string &s) {
ss << s;
}
~throw_error() noexcept(false) {
throw std::runtime_error(ss.str());
// throw device_error(ss.str());
}
template<class T>
throw_error &operator<<(const T &val) {
ss << val;
return *this;
}
std::ostringstream ss;
};
static int xioctl(int fh, int request, void *arg) {
int r;
do {
r = ioctl(fh, request, arg);
} while (r < 0 && errno == EINTR);
return r;
}
struct buffer {
void *start;
size_t length;
};
struct context {
context() {
VLOG(2) << __func__;
}
~context() {
VLOG(2) << __func__;
}
};
struct device {
const std::shared_ptr<context> parent;
std::string dev_name; // Device name (typically of the form /dev/video*)
std::string name; // Device description name
int vid, pid, mi; // Vendor ID, product ID, and multiple interface index
int fd = -1; // File descriptor for this device
int width, height, format, fps;
video_channel_callback callback = nullptr;
bool is_capturing = false;
std::vector<buffer> buffers;
std::thread thread;
volatile bool stop = false;
device(std::shared_ptr<context> parent, const std::string &name)
: parent(parent), dev_name("/dev/" + name) {
VLOG(2) << __func__ << ": " << dev_name;
struct stat st;
if (stat(dev_name.c_str(), &st) < 0) { // file status
throw_error() << "Cannot identify '" << dev_name << "': " << errno << ", "
<< strerror(errno);
}
if (!S_ISCHR(st.st_mode)) { // character device?
throw_error() << dev_name << " is no device";
}
if (!(std::ifstream("/sys/class/video4linux/" + name + "/name") >>
this->name))
throw_error() << "Failed to read name";
std::string modalias;
if (!(std::ifstream(
"/sys/class/video4linux/" + name + "/device/modalias") >>
modalias))
throw_error() << "Failed to read modalias";
if (modalias.size() < 14 || modalias.substr(0, 5) != "usb:v" ||
modalias[9] != 'p')
throw_error() << "Not a usb format modalias";
if (!(std::istringstream(modalias.substr(5, 4)) >> std::hex >> vid))
throw_error() << "Failed to read vendor ID";
if (!(std::istringstream(modalias.substr(10, 4)) >> std::hex >> pid))
throw_error() << "Failed to read product ID";
if (!(std::ifstream(
"/sys/class/video4linux/" + name + "/device/bInterfaceNumber") >>
std::hex >> mi))
throw_error() << "Failed to read interface number";
fd = open(dev_name.c_str(), O_RDWR | O_NONBLOCK, 0);
if (fd < 0) {
throw_error() << "Cannot open '" << dev_name << "': " << errno << ", "
<< strerror(errno);
}
v4l2_capability cap;
if (xioctl(fd, VIDIOC_QUERYCAP, &cap) < 0) {
if (errno == EINVAL)
throw_error() << dev_name << " is no V4L2 device";
else
throw_error() << "VIDIOC_QUERYCAP error " << errno << ", "
<< strerror(errno);
}
if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
throw_error() << dev_name + " is no video capture device";
if (!(cap.capabilities & V4L2_CAP_STREAMING))
throw_error() << dev_name + " does not support streaming I/O";
// Select video input, video standard and tune here.
v4l2_cropcap cropcap;
cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (xioctl(fd, VIDIOC_CROPCAP, &cropcap) == 0) {
v4l2_crop crop;
crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
crop.c = cropcap.defrect; // reset to default
if (xioctl(fd, VIDIOC_S_CROP, &crop) < 0) {
switch (errno) {
case EINVAL:
break; // Cropping not supported
default:
break; // Errors ignored
}
}
} else {
} // Errors ignored
}
~device() {
VLOG(2) << __func__;
stop_streaming();
no_data_count = 0;
if (fd != -1 && close(fd) < 0) {
LOG_ERROR(WARNING, "close");
}
}
bool pu_control_range(
uint32_t id, int32_t *min, int32_t *max, int32_t *def) const {
struct v4l2_queryctrl query;
query.id = id;
if (xioctl(fd, VIDIOC_QUERYCTRL, &query) < 0) {
LOG_ERROR(WARNING, "pu_control_range failed");
return false;
}
if (min)
*min = query.minimum;
if (max)
*max = query.maximum;
if (def)
*def = query.default_value;
return true;
}
bool pu_control_query(uint32_t id, int query, int32_t *value) const {
CHECK_NOTNULL(value);
struct v4l2_control control = {id, *value};
if (xioctl(fd, query, &control) < 0) {
LOG_ERROR(WARNING, "pu_control_query failed");
return false;
}
*value = control.value;
return true;
}
bool xu_control_query(
const xu &xu, uint8_t selector, uint8_t query, uint16_t size,
uint8_t *data) const {
CHECK_NOTNULL(data);
uvc_xu_control_query q = {xu.unit, selector, query, size, data};
if (xioctl(fd, UVCIOC_CTRL_QUERY, &q) < 0) {
LOG_ERROR(WARNING, "xu_control_query failed");
return false;
}
return true;
}
void set_format(
int width, int height, int fourcc, int fps,
video_channel_callback callback) {
this->width = width;
this->height = height;
this->format = fourcc;
this->fps = fps;
this->callback = callback;
}
void start_capture() {
if (is_capturing) {
LOG(WARNING) << "Start capture failed, is capturing already";
return;
}
v4l2_format fmt;
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = width;
fmt.fmt.pix.height = height;
fmt.fmt.pix.pixelformat = format;
fmt.fmt.pix.field = V4L2_FIELD_NONE;
// fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
if (xioctl(fd, VIDIOC_S_FMT, &fmt) < 0)
LOG_ERROR(FATAL, "VIDIOC_S_FMT");
v4l2_streamparm parm;
parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (xioctl(fd, VIDIOC_G_PARM, &parm) < 0)
LOG_ERROR(FATAL, "VIDIOC_G_PARM");
parm.parm.capture.timeperframe.numerator = 1;
parm.parm.capture.timeperframe.denominator = fps;
if (xioctl(fd, VIDIOC_S_PARM, &parm) < 0)
LOG_ERROR(FATAL, "VIDIOC_S_PARM");
// Init memory mapped IO
v4l2_requestbuffers req;
req.count = 24;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
if (xioctl(fd, VIDIOC_REQBUFS, &req) < 0) {
if (errno == EINVAL)
LOG(FATAL) << dev_name << " does not support memory mapping";
else
LOG_ERROR(FATAL, "VIDIOC_REQBUFS");
}
if (req.count < 2) {
LOG(FATAL) << "Insufficient buffer memory on " << dev_name;
}
buffers.resize(req.count);
for (size_t i = 0; i < buffers.size(); ++i) {
v4l2_buffer buf;
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if (xioctl(fd, VIDIOC_QUERYBUF, &buf) < 0)
LOG_ERROR(FATAL, "VIDIOC_QUERYBUF");
buffers[i].length = buf.length;
buffers[i].start = mmap(
NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd,
buf.m.offset);
if (buffers[i].start == MAP_FAILED)
LOG_ERROR(FATAL, "mmap");
}
// Start capturing
for (size_t i = 0; i < buffers.size(); ++i) {
v4l2_buffer buf;
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if (xioctl(fd, VIDIOC_QBUF, &buf) < 0)
LOG_ERROR(FATAL, "VIDIOC_QBUF");
}
v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
for (int i = 0; i < 10; ++i) {
if (xioctl(fd, VIDIOC_STREAMON, &type) < 0) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
if (xioctl(fd, VIDIOC_STREAMON, &type) < 0)
LOG_ERROR(FATAL, "VIDIOC_STREAMON");
is_capturing = true;
}
void stop_capture() {
if (!is_capturing)
return;
// Stop streamining
v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (xioctl(fd, VIDIOC_STREAMOFF, &type) < 0)
LOG_ERROR(WARNING, "VIDIOC_STREAMOFF");
for (size_t i = 0; i < buffers.size(); i++) {
if (munmap(buffers[i].start, buffers[i].length) < 0)
LOG_ERROR(WARNING, "munmap");
}
// Close memory mapped IO
struct v4l2_requestbuffers req;
req.count = 0;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
if (xioctl(fd, VIDIOC_REQBUFS, &req) < 0) {
if (errno == EINVAL)
LOG(ERROR) << dev_name << " does not support memory mapping";
else
LOG_ERROR(WARNING, "VIDIOC_REQBUFS");
}
is_capturing = false;
}
void poll() {
fd_set fds;
FD_ZERO(&fds);
FD_SET(fd, &fds);
struct timeval tv = {0, 10000};
if (select(fd + 1, &fds, NULL, NULL, &tv) < 0) {
if (errno == EINTR)
return;
LOG_ERROR(FATAL, "select");
}
if (FD_ISSET(fd, &fds)) {
v4l2_buffer buf;
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
if (xioctl(fd, VIDIOC_DQBUF, &buf) < 0) {
if (errno == EAGAIN)
return;
LOG_ERROR(FATAL, "VIDIOC_DQBUF");
}
if (callback) {
callback(buffers[buf.index].start, [buf, this]() mutable {
if (xioctl(fd, VIDIOC_QBUF, &buf) < 0)
throw_error("VIDIOC_QBUF");
});
}
no_data_count = 0;
} else {
no_data_count++;
}
if (no_data_count > NO_DATA_MAX_COUNT) {
throw_error("v4l2 get stream time out!");
}
}
void start_streaming() {
if (!callback) {
LOG(WARNING) << __func__ << " failed: video_channel_callback is empty";
return;
}
start_capture();
thread = std::thread([this]() {
while (!stop)
poll();
});
}
void stop_streaming() {
if (thread.joinable()) {
stop = true;
thread.join();
stop = false;
stop_capture();
}
}
};
std::shared_ptr<context> create_context() {
return std::make_shared<context>();
}
std::vector<std::shared_ptr<device>> query_devices(
std::shared_ptr<context> context) {
std::vector<std::shared_ptr<device>> devices;
DIR *dir = opendir("/sys/class/video4linux");
if (!dir)
LOG(FATAL) << "Cannot access /sys/class/video4linux";
while (dirent *entry = readdir(dir)) {
std::string name = entry->d_name;
if (name == "." || name == "..")
continue;
// Resolve a pathname to ignore virtual video devices
std::string path = "/sys/class/video4linux/" + name;
char buff[PATH_MAX];
ssize_t len = ::readlink(path.c_str(), buff, sizeof(buff) - 1);
if (len != -1) {
buff[len] = '\0';
std::string real_path = std::string(buff);
if (real_path.find("virtual") != std::string::npos)
continue;
}
try {
devices.push_back(std::make_shared<device>(context, name));
} catch (const std::exception &e) {
VLOG(2) << "Not a USB video device: " << e.what();
}
}
closedir(dir);
return devices;
}
std::string get_name(const device &device) {
return device.name;
}
int get_vendor_id(const device &device) {
return device.vid;
}
int get_product_id(const device &device) {
return device.pid;
}
std::string get_video_name(const device &device) {
return device.dev_name;
}
static uint32_t get_cid(Option option) {
switch (option) {
case Option::GAIN:
return V4L2_CID_GAIN;
case Option::BRIGHTNESS:
return V4L2_CID_BRIGHTNESS;
case Option::CONTRAST:
return V4L2_CID_CONTRAST;
default:
LOG(FATAL) << "No v4l2 cid for " << option;
}
}
bool pu_control_range(
const device &device, Option option, int32_t *min, int32_t *max,
int32_t *def) {
return device.pu_control_range(get_cid(option), min, max, def);
}
bool pu_control_query(
const device &device, Option option, pu_query query, int32_t *value) {
int code;
switch (query) {
case PU_QUERY_SET:
code = VIDIOC_S_CTRL;
break;
case PU_QUERY_GET:
code = VIDIOC_G_CTRL;
break;
default:
LOG(ERROR) << "pu_control_query request code is unaccepted";
return false;
}
return device.pu_control_query(get_cid(option), code, value);
}
bool xu_control_range(
const device &device, const xu &xu, uint8_t selector, uint8_t id,
int32_t *min, int32_t *max, int32_t *def) {
bool ret = true;
std::uint8_t data[3]{static_cast<uint8_t>(id | 0x80), 0, 0};
if (!xu_control_query(device, xu, selector, XU_QUERY_SET, 3, data)) {
LOG(WARNING) << "xu_control_range query failed";
ret = false;
}
if (xu_control_query(device, xu, selector, XU_QUERY_MIN, 3, data)) {
*min = (data[1] << 8) | (data[2]);
} else {
LOG(WARNING) << "xu_control_range query min failed";
ret = false;
}
if (xu_control_query(device, xu, selector, XU_QUERY_MAX, 3, data)) {
*max = (data[1] << 8) | (data[2]);
} else {
LOG(WARNING) << "xu_control_range query max failed";
ret = false;
}
if (xu_control_query(device, xu, selector, XU_QUERY_DEF, 3, data)) {
*def = (data[1] << 8) | (data[2]);
} else {
LOG(WARNING) << "xu_control_range query def failed";
ret = false;
}
return ret;
}
bool xu_control_query(
const device &device, const xu &xu, uint8_t selector, xu_query query,
uint16_t size, uint8_t *data) {
uint8_t code;
switch (query) {
case XU_QUERY_SET:
code = UVC_SET_CUR;
break;
case XU_QUERY_GET:
code = UVC_GET_CUR;
break;
case XU_QUERY_MIN:
code = UVC_GET_MIN;
break;
case XU_QUERY_MAX:
code = UVC_GET_MAX;
break;
case XU_QUERY_DEF:
code = UVC_GET_DEF;
break;
default:
LOG(ERROR) << "xu_control_query request code is unaccepted";
return false;
}
return device.xu_control_query(xu, selector, code, size, data);
}
void set_device_mode(
device &device, int width, int height, int fourcc, int fps, // NOLINT
video_channel_callback callback) {
device.set_format(width, height, fourcc, fps, callback);
}
void start_streaming(device &device, int /*num_transfer_bufs*/) { // NOLINT
device.start_streaming();
}
void stop_streaming(device &device) { // NOLINT
device.stop_streaming();
}
} // namespace uvc
MYNTEYE_END_NAMESPACE
// Video4Linux (V4L) driver-specific documentation
// https://linuxtv.org/downloads/v4l-dvb-apis/v4l-drivers/index.html

782
src/mynteye/uvc/uvc-wmf.cc Normal file
View File

@@ -0,0 +1,782 @@
// Copyright 2018 Slightech Co., Ltd. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "mynteye/uvc/uvc.h"
#include <windows.h>
#include <usbioctl.h>
#include <Shlwapi.h> // For QISearch, etc.
#include <mfapi.h> // For MFStartup, etc.
#include <mfidl.h> // For MF_DEVSOURCE_*, etc.
#include <mfreadwrite.h> // MFCreateSourceReaderFromMediaSource
#include <mferror.h>
#pragma comment(lib, "Shlwapi.lib")
#pragma comment(lib, "mf.lib")
#pragma comment(lib, "mfplat.lib")
#pragma comment(lib, "mfreadwrite.lib")
#pragma comment(lib, "mfuuid.lib")
#pragma comment(lib, "setupapi.lib")
#pragma comment(lib, "winusb.lib")
#include <uuids.h>
#include <vidcap.h>
#include <ksmedia.h>
#include <ksproxy.h>
#include <Cfgmgr32.h>
#pragma comment(lib, "cfgmgr32.lib")
#include <SetupAPI.h>
#include <WinUsb.h>
#include <algorithm>
#include <chrono>
#include <functional>
#include <map>
#include <regex>
#include <sstream>
#include <thread>
#include <strsafe.h>
#include "mynteye/logger.h"
#define VLOG_INFO VLOG(2)
// #define VLOG_INFO LOG(INFO)
MYNTEYE_BEGIN_NAMESPACE
namespace uvc {
const std::map<uint32_t, uint32_t> fourcc_map = {
{ 0x56595559, 0x32595559 }, // 'VYUY' => '2YUY'
{ 0x59555956, 0x59555932 } // 'YUYV' => 'YUY2'
};
struct throw_error {
throw_error() = default;
explicit throw_error(const std::string &s) {
ss << s;
}
~throw_error() noexcept(false) {
throw std::runtime_error(ss.str());
}
template<class T>
throw_error &operator<<(const T &val) {
ss << val;
return *this;
}
std::ostringstream ss;
};
static void check(const char *call, HRESULT hr) {
if (FAILED(hr)) {
throw_error() << call << "(...) returned 0x" << std::hex
<< static_cast<uint32_t>(hr);
} else {
// VLOG_INFO << call << " SUCCESSED";
}
}
template<class T> class com_ptr {
T *p;
void ref(T *new_p) {
if (p == new_p) return;
unref();
p = new_p;
if (p) p->AddRef();
}
void unref() {
if (p) {
p->Release();
p = nullptr;
}
}
public:
com_ptr() : p() {}
com_ptr(T *p) : com_ptr() {
ref(p);
}
com_ptr(const com_ptr &r) : com_ptr(r.p) {}
~com_ptr() {
unref();
}
operator T *() const {
return p;
}
T &operator*() const {
return *p;
}
T *operator->() const {
return p;
}
T **operator&() {
unref();
return &p;
}
com_ptr &operator=(const com_ptr &r) {
ref(r.p);
return *this;
}
};
static std::string win_to_utf(const WCHAR *s) {
int len = WideCharToMultiByte(CP_UTF8, 0, s, -1, nullptr, 0, NULL, NULL);
if (len == 0) throw_error() << "WideCharToMultiByte(...) returned 0 and GetLastError() is " << GetLastError();
std::string buffer(len - 1, ' ');
len = WideCharToMultiByte(CP_UTF8, 0, s, -1, &buffer[0], (int)buffer.size()+1, NULL, NULL);
if (len == 0) throw_error() << "WideCharToMultiByte(...) returned 0 and GetLastError() is " << GetLastError();
return buffer;
}
std::vector<std::string> tokenize(std::string string, char separator) {
std::vector<std::string> tokens;
std::string::size_type i1 = 0;
while (true) {
auto i2 = string.find(separator, i1);
if (i2 == std::string::npos) {
tokens.push_back(string.substr(i1));
return tokens;
}
tokens.push_back(string.substr(i1, i2-i1));
i1 = i2 + 1;
}
}
/*
static void print_guid(const char *call, int i, GUID guid) {
std::ostringstream ss;
ss << call << "(" << i << ") = ";
ss << "Data1: " << std::hex << guid.Data1 << ", ";
ss << "Data2: " << std::hex << guid.Data2 << ", ";
ss << "Data3: " << std::hex << guid.Data3 << ", ";
ss << "Data4: [ ";
for (int j = 0; j < 8; j++) {
ss << std::hex << (int)guid.Data4[j] << " ";
}
ss << "]";
LOG(INFO) << ss.str();
}
*/
bool parse_usb_path(int &vid, int &pid, int &mi, std::string &unique_id, const std::string &path) {
auto name = path;
std::transform(begin(name), end(name), begin(name), ::tolower);
auto tokens = tokenize(name, '#');
if (tokens.size() < 1 || tokens[0] != R"(\\?\usb)") return false; // Not a USB device
if (tokens.size() < 3) {
LOG(ERROR) << "malformed usb device path: " << name;
return false;
}
auto ids = tokenize(tokens[1], '&');
if (ids[0].size() != 8 || ids[0].substr(0,4) != "vid_" || !(std::istringstream(ids[0].substr(4,4)) >> std::hex >> vid)) {
LOG(ERROR) << "malformed vid string: " << tokens[1];
return false;
}
if (ids[1].size() != 8 || ids[1].substr(0,4) != "pid_" || !(std::istringstream(ids[1].substr(4,4)) >> std::hex >> pid)) {
LOG(ERROR) << "malformed pid string: " << tokens[1];
return false;
}
if (ids[2].size() != 5 || ids[2].substr(0,3) != "mi_" || !(std::istringstream(ids[2].substr(3,2)) >> mi)) {
LOG(ERROR) << "malformed mi string: " << tokens[1];
return false;
}
ids = tokenize(tokens[2], '&');
if (ids.size() < 2) {
LOG(ERROR) << "malformed id string: " << tokens[2];
return false;
}
unique_id = ids[1];
return true;
}
bool parse_usb_path_from_device_id(int &vid, int &pid, int &mi, std::string &unique_id, const std::string &device_id) {
auto name = device_id;
std::transform(begin(name), end(name), begin(name), ::tolower);
auto tokens = tokenize(name, '\\');
if (tokens.size() < 1 || tokens[0] != R"(usb)") return false; // Not a USB device
auto ids = tokenize(tokens[1], '&');
if (ids[0].size() != 8 || ids[0].substr(0, 4) != "vid_" || !(std::istringstream(ids[0].substr(4, 4)) >> std::hex >> vid)) {
LOG(ERROR) << "malformed vid string: " << tokens[1];
return false;
}
if (ids[1].size() != 8 || ids[1].substr(0, 4) != "pid_" || !(std::istringstream(ids[1].substr(4, 4)) >> std::hex >> pid)) {
LOG(ERROR) << "malformed pid string: " << tokens[1];
return false;
}
if (ids[2].size() != 5 || ids[2].substr(0, 3) != "mi_" || !(std::istringstream(ids[2].substr(3, 2)) >> mi)) {
LOG(ERROR) << "malformed mi string: " << tokens[1];
return false;
}
ids = tokenize(tokens[2], '&');
if (ids.size() < 2) {
LOG(ERROR) << "malformed id string: " + tokens[2];
return false;
}
unique_id = ids[1];
return true;
}
struct context {
context() {
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
MFStartup(MF_VERSION, MFSTARTUP_NOSOCKET);
}
~context() {
MFShutdown();
CoUninitialize();
}
};
class reader_callback : public IMFSourceReaderCallback {
std::weak_ptr<device> owner; // The device holds a reference to us, so use
// weak_ptr to prevent a cycle
ULONG ref_count;
volatile bool streaming = false;
public:
reader_callback(std::weak_ptr<device> owner) : owner(owner), ref_count() {}
bool is_streaming() const {
return streaming;
}
void on_start() {
streaming = true;
}
#pragma warning( push )
#pragma warning( disable: 4838 )
// Implement IUnknown
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) override {
static const QITAB table[] = {QITABENT(reader_callback, IUnknown), QITABENT(reader_callback, IMFSourceReaderCallback), {0}};
return QISearch(this, table, riid, ppvObject);
}
#pragma warning( pop )
ULONG STDMETHODCALLTYPE AddRef() override { return InterlockedIncrement(&ref_count); }
ULONG STDMETHODCALLTYPE Release() override {
ULONG count = InterlockedDecrement(&ref_count);
if (count == 0) delete this;
return count;
}
// Implement IMFSourceReaderCallback
HRESULT STDMETHODCALLTYPE OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex, DWORD dwStreamFlags, LONGLONG llTimestamp, IMFSample *sample) override;
HRESULT STDMETHODCALLTYPE OnFlush(DWORD dwStreamIndex) override { streaming = false; return S_OK; }
HRESULT STDMETHODCALLTYPE OnEvent(DWORD dwStreamIndex, IMFMediaEvent *pEvent) override { return S_OK; }
};
struct device {
const std::shared_ptr<context> parent;
int vid, pid;
std::string unique_id;
std::string name;
com_ptr<reader_callback> reader_callback;
com_ptr<IMFActivate> mf_activate;
com_ptr<IMFMediaSource> mf_media_source;
com_ptr<IAMCameraControl> am_camera_control;
com_ptr<IAMVideoProcAmp> am_video_proc_amp;
std::map<int, com_ptr<IKsControl>> ks_controls;
com_ptr<IMFSourceReader> mf_source_reader;
video_channel_callback callback = nullptr;
device(std::shared_ptr<context> parent, int vid, int pid, std::string unique_id, std::string name)
: parent(move(parent)), vid(vid), pid(pid), unique_id(move(unique_id)), name(name) {
}
~device() {
stop_streaming();
}
IKsControl *get_ks_control(const uvc::xu &xu) {
auto it = ks_controls.find(xu.node);
if (it != end(ks_controls)) return it->second;
get_media_source();
// Attempt to retrieve IKsControl
com_ptr<IKsTopologyInfo> ks_topology_info = NULL;
check("QueryInterface", mf_media_source->QueryInterface(__uuidof(IKsTopologyInfo), (void **)&ks_topology_info));
GUID node_type;
/*
DWORD numberOfNodes;
check("get_NumNodes", ks_topology_info->get_NumNodes(&numberOfNodes));
for (int i = 0; i < numberOfNodes; i++) {
check("get_NodeType", ks_topology_info->get_NodeType(i, &node_type));
print_guid("node_type", i, node_type);
}
*/
check("get_NodeType", ks_topology_info->get_NodeType(xu.node, &node_type));
const GUID KSNODETYPE_DEV_SPECIFIC_LOCAL{0x941C7AC0L, 0xC559, 0x11D0, {0x8A, 0x2B, 0x00, 0xA0, 0xC9, 0x25, 0x5A, 0xC1}};
if (node_type != KSNODETYPE_DEV_SPECIFIC_LOCAL) throw_error() << "Invalid extension unit node ID: " << xu.node;
com_ptr<IUnknown> unknown;
check("CreateNodeInstance", ks_topology_info->CreateNodeInstance(xu.node, IID_IUnknown, (LPVOID *)&unknown));
com_ptr<IKsControl> ks_control;
check("QueryInterface", unknown->QueryInterface(__uuidof(IKsControl), (void **)&ks_control));
VLOG_INFO << "Obtained KS control node : " << xu.node;
return ks_controls[xu.node] = ks_control;
}
void start_streaming() {
if (mf_source_reader) {
reader_callback->on_start();
check("IMFSourceReader::ReadSample", mf_source_reader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, NULL, NULL, NULL, NULL));
}
}
void stop_streaming() {
if (mf_source_reader) mf_source_reader->Flush(MF_SOURCE_READER_FIRST_VIDEO_STREAM);
while (true) {
bool is_streaming = reader_callback->is_streaming();
if (is_streaming) std::this_thread::sleep_for(std::chrono::milliseconds(10));
else break;
}
mf_source_reader = nullptr;
am_camera_control = nullptr;
am_video_proc_amp = nullptr;
ks_controls.clear();
if (mf_media_source) {
mf_media_source = nullptr;
check("IMFActivate::ShutdownObject", mf_activate->ShutdownObject());
}
callback = {};
}
com_ptr<IMFMediaSource> get_media_source() {
if (!mf_media_source) {
check("IMFActivate::ActivateObject", mf_activate->ActivateObject(__uuidof(IMFMediaSource), (void **)&mf_media_source));
if (mf_media_source) {
check("IMFMediaSource::QueryInterface", mf_media_source->QueryInterface(__uuidof(IAMCameraControl), (void **)&am_camera_control));
if (SUCCEEDED(mf_media_source->QueryInterface(__uuidof(IAMVideoProcAmp), (void **)&am_video_proc_amp)));
} else throw_error() << "Invalid media source";
}
return mf_media_source;
}
};
HRESULT reader_callback::OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex, DWORD dwStreamFlags, LONGLONG llTimestamp, IMFSample *sample) {
if (auto owner_ptr = owner.lock()) {
if (sample) {
com_ptr<IMFMediaBuffer> buffer = NULL;
if (SUCCEEDED(sample->GetBufferByIndex(0, &buffer))) {
BYTE *byte_buffer;
DWORD max_length, current_length;
if (SUCCEEDED(buffer->Lock(&byte_buffer, &max_length, &current_length))) {
auto continuation = [buffer, this]() {
buffer->Unlock();
};
owner_ptr->callback(byte_buffer, continuation);
}
}
}
if (auto owner_ptr_new = owner.lock()) {
auto hr = owner_ptr_new->mf_source_reader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, NULL, NULL, NULL, NULL);
switch (hr) {
case S_OK: break;
case MF_E_INVALIDREQUEST: LOG(ERROR) << "ReadSample returned MF_E_INVALIDREQUEST"; break;
case MF_E_INVALIDSTREAMNUMBER: LOG(ERROR) << "ReadSample returned MF_E_INVALIDSTREAMNUMBER"; break;
case MF_E_NOTACCEPTING: LOG(ERROR) << "ReadSample returned MF_E_NOTACCEPTING"; break;
case E_INVALIDARG: LOG(ERROR) << "ReadSample returned E_INVALIDARG"; break;
case MF_E_VIDEO_RECORDING_DEVICE_INVALIDATED: LOG(ERROR) << "ReadSample returned MF_E_VIDEO_RECORDING_DEVICE_INVALIDATED"; break;
default: LOG(ERROR) << "ReadSample returned HRESULT " << std::hex << (uint32_t)hr; break;
}
if (hr != S_OK) streaming = false;
}
}
return S_OK;
}
std::shared_ptr<context> create_context() {
return std::make_shared<context>();
}
std::vector<std::shared_ptr<device>> query_devices(std::shared_ptr<context> context) {
IMFAttributes *pAttributes = NULL;
check("MFCreateAttributes", MFCreateAttributes(&pAttributes, 1));
check("IMFAttributes::SetGUID", pAttributes->SetGUID(
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID));
IMFActivate **ppDevices;
UINT32 numDevices;
check("MFEnumDeviceSources", MFEnumDeviceSources(pAttributes, &ppDevices, &numDevices));
std::vector<std::shared_ptr<device>> devices;
for (UINT32 i = 0; i < numDevices; ++i) {
com_ptr<IMFActivate> pDevice;
*&pDevice = ppDevices[i];
WCHAR *wchar_dev_name = NULL;
WCHAR *wchar_name = NULL;
UINT32 length;
pDevice->GetAllocatedString(
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, &wchar_dev_name,
&length);
auto dev_name = win_to_utf(wchar_dev_name);
CoTaskMemFree(wchar_dev_name);
pDevice->GetAllocatedString(MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, &wchar_name, &length);
auto name = win_to_utf(wchar_name); // Device description name
CoTaskMemFree(wchar_name);
int vid, pid, mi;
std::string unique_id;
if (!parse_usb_path(vid, pid, mi, unique_id, dev_name)) continue;
std::shared_ptr<device> dev;
for (auto & d : devices) {
if (d->vid == vid && d->pid == pid && d->unique_id == unique_id)
dev = d;
}
if (!dev) {
try {
dev = std::make_shared<device>(context, vid, pid, unique_id, name);
devices.push_back(dev);
} catch (const std::exception &e) {
VLOG_INFO << "Not a USB video device: " << e.what();
}
}
dev->reader_callback = new reader_callback(dev);
dev->mf_activate = pDevice;
dev->vid = vid;
dev->pid = pid;
}
CoTaskMemFree(ppDevices);
return devices;
}
int get_vendor_id(const device &device) {
return device.vid;
}
int get_product_id(const device &device) {
return device.pid;
}
std::string get_name(const device &device) {
return device.name;
}
std::string get_video_name(const device &device) {
return device.name;
}
static long get_cid(Option option) {
switch (option) {
case Option::GAIN:
return VideoProcAmp_Gain;
case Option::BRIGHTNESS:
return VideoProcAmp_Brightness;
case Option::CONTRAST:
return VideoProcAmp_Contrast;
default:
LOG(FATAL) << "No VideoProcAmp cid for " << option;
}
}
bool pu_control_range(
const device &device, Option option, int32_t *min, int32_t *max,
int32_t *def) {
VLOG_INFO << __func__ << " " << option;
const_cast<uvc::device &>(device).get_media_source();
long minVal = 0, maxVal = 0, steppingDelta = 0, defVal = 0, capsFlag = 0;
check("IAMVideoProcAmp::GetRange",
const_cast<uvc::device &>(device).am_video_proc_amp->GetRange(
get_cid(option), &minVal, &maxVal, &steppingDelta, &defVal, &capsFlag));
if (min) *min = static_cast<int>(minVal);
if (max) *max = static_cast<int>(maxVal);
if (def) *def = static_cast<int>(defVal);
VLOG_INFO << __func__ << " " << option <<
": min=" << *min << ", max=" << *max << ", def=" << *def;
return true;
}
static void pu_control_get(const device &device, long property, int32_t *value) {
long data, flags = 0;
check("IAMVideoProcAmp::Get",
const_cast<uvc::device &>(device).am_video_proc_amp->Get(
property, &data, &flags));
*value = data;
}
static void pu_control_set(const device &device, long property, int32_t *value) {
long data = *value;
check("IAMVideoProcAmp::Set",
const_cast<uvc::device &>(device).am_video_proc_amp->Set(
property, data, VideoProcAmp_Flags_Auto));
}
bool pu_control_query(
const device &device, Option option, pu_query query, int32_t *value) {
CHECK_NOTNULL(value);
const_cast<uvc::device &>(device).get_media_source();
switch (query) {
case PU_QUERY_SET:
VLOG_INFO << "pu_control_set " << option << ": " << *value;
pu_control_set(device, get_cid(option), value);
VLOG_INFO << "pu_control_set " << option << " done";
return true;
case PU_QUERY_GET:
VLOG_INFO << "pu_control_get " << option;
pu_control_get(device, get_cid(option), value);
VLOG_INFO << "pu_control_get " << option << ": " << *value;
return true;
default:
LOG(ERROR) << "pu_control_query request code is unaccepted";
return false;
}
}
static std::string to_string(uint16_t size, uint8_t *data) {
std::ostringstream ss;
for (uint8_t *beg = data, *end = data + size; beg != end; beg++) {
ss << "0x" << std::hex << static_cast<int>(*beg) << ",";
}
return ss.str();
}
/*
static std::vector<BYTE> xu_control_desc(const device &device, const xu &xu, ULONG id, ULONG flags) {
auto ks_control = const_cast<uvc::device &>(device).get_ks_control(xu);
KSP_NODE node;
memset(&node, 0, sizeof(KSP_NODE));
node.Property.Set = reinterpret_cast<const GUID &>(xu.id);
node.Property.Id = id;
node.Property.Flags = flags;
node.NodeId = xu.node;
KSPROPERTY_DESCRIPTION description;
ULONG bytes_received = 0;
check("IKsControl::KsProperty", ks_control->KsProperty(
(PKSPROPERTY)&node,
sizeof(node),
&description,
sizeof(KSPROPERTY_DESCRIPTION),
&bytes_received));
ULONG size = description.DescriptionSize;
std::vector<BYTE> buffer(size);
check("IKsControl::KsProperty", ks_control->KsProperty(
(PKSPROPERTY)&node,
sizeof(node),
buffer.data(),
size,
&bytes_received));
if (bytes_received != size) { throw_error() << "wrong data"; }
// VLOG_INFO << "buffer size=" << size << ", data=["
// << to_string(size, buffer.data()) << "]";
return buffer;
}
bool xu_control_range(
const device &device, const xu &xu, uint8_t selector, int32_t *min,
int32_t *max, int32_t *def) {
VLOG_INFO << __func__ << " " << static_cast<int>(selector);
size_t prop_header_size = sizeof(KSPROPERTY_MEMBERSHEADER) + sizeof(KSPROPERTY_DESCRIPTION);
// get step, min and max values
{
auto &&buffer = xu_control_desc(device, xu, selector,
KSPROPERTY_TYPE_BASICSUPPORT | KSPROPERTY_TYPE_TOPOLOGY);
BYTE *values = buffer.data() + prop_header_size;
// size_t size = buffer.size() - prop_header_size;
// VLOG_INFO << "values size: " << size << ", data=["
// << to_string(size, values) << "]";
*min = (values[1] << 8) | (values[2]);
values += 3;
*max = (values[1] << 8) | (values[2]);
// values += 3;
// *step = (values[1] << 8) | (values[2]);
}
// get def value
{
auto &&buffer = xu_control_desc(device, xu, selector,
KSPROPERTY_TYPE_DEFAULTVALUES | KSPROPERTY_TYPE_TOPOLOGY);
BYTE *values = buffer.data() + prop_header_size;
// size_t size = buffer.size() - prop_header_size;
// VLOG_INFO << "values size: " << size << ", data=["
// << to_string(size, values) << "]";
*def = (values[1] << 8) | (values[2]);
}
VLOG_INFO << __func__ << " " << static_cast<int>(selector)
<< ": min=" << *min << ", max=" << *max << ", def=" << *def;
return true;
}
*/
static void xu_control_get(const device &device, const xu &xu, uint8_t selector,
uint16_t size, uint8_t *data) {
VLOG_INFO << __func__ << " " << static_cast<int>(selector);
auto &&ks_control = const_cast<uvc::device &>(device).get_ks_control(xu);
KSP_NODE node;
memset(&node, 0, sizeof(KSP_NODE));
node.Property.Set = reinterpret_cast<const GUID &>(xu.id);
node.Property.Id = selector;
node.Property.Flags = KSPROPERTY_TYPE_GET | KSPROPERTY_TYPE_TOPOLOGY;
node.NodeId = xu.node;
ULONG bytes_received = 0;
check("IKsControl::KsProperty", ks_control->KsProperty(
(PKSPROPERTY)&node, sizeof(node), data, size, &bytes_received));
if (bytes_received != size)
throw_error() << "xu_control_get did not return enough data";
VLOG_INFO << __func__ << " " << static_cast<int>(selector)
<< ": size=" << size << ", data=[" << to_string(size, data) << "]";
}
static void xu_control_set(const device &device, const xu &xu, uint8_t selector,
uint16_t size, uint8_t *data) {
VLOG_INFO << __func__ << " " << static_cast<int>(selector)
<< ": size=" << size << ", data=[" << to_string(size, data) << "]";
auto &&ks_control = const_cast<uvc::device &>(device).get_ks_control(xu);
KSP_NODE node;
memset(&node, 0, sizeof(KSP_NODE));
node.Property.Set = reinterpret_cast<const GUID &>(xu.id);
node.Property.Id = selector;
node.Property.Flags = KSPROPERTY_TYPE_SET | KSPROPERTY_TYPE_TOPOLOGY;
node.NodeId = xu.node;
ULONG bytes_received = 0;
check("IKsControl::KsProperty", ks_control->KsProperty(
(PKSPROPERTY)&node, sizeof(node), data, size, &bytes_received));
VLOG_INFO << __func__ << " " << static_cast<int>(selector) << " done";
}
static int32_t xu_control_range_basic(const device &device, const xu &xu, uint8_t selector, uint8_t id) {
std::uint8_t data[3]{id, 0, 0};
xu_control_set(device, xu, selector, 3, data);
xu_control_get(device, xu, selector, 3, data);
return (data[1] << 8) | (data[2]);
}
bool xu_control_range(
const device &device, const xu &xu, uint8_t selector, uint8_t id,
int32_t *min, int32_t *max, int32_t *def) {
VLOG_INFO << __func__ << " " << static_cast<int>(selector);
*min = xu_control_range_basic(
device, xu, selector, static_cast<uint8_t>(id | 0x90));
*max = xu_control_range_basic(
device, xu, selector, static_cast<uint8_t>(id | 0xa0));
*def = xu_control_range_basic(
device, xu, selector, static_cast<uint8_t>(id | 0xc0));
return true;
}
bool xu_control_query(
const device &device, const xu &xu, uint8_t selector, xu_query query,
uint16_t size, uint8_t *data) {
CHECK_NOTNULL(data);
switch (query) {
case XU_QUERY_SET:
xu_control_set(device, xu, selector, size, data);
return true;
case XU_QUERY_GET:
xu_control_get(device, xu, selector, size, data);
return true;
default:
LOG(ERROR) << "xu_control_query request code is unaccepted";
return false;
}
}
void set_device_mode(device &device, int width, int height, int fourcc, int fps, video_channel_callback callback) {
if (!device.mf_source_reader) {
com_ptr<IMFAttributes> pAttributes;
check("MFCreateAttributes", MFCreateAttributes(&pAttributes, 1));
check("IMFAttributes::SetUnknown", pAttributes->SetUnknown(MF_SOURCE_READER_ASYNC_CALLBACK, static_cast<IUnknown *>(device.reader_callback)));
check("MFCreateSourceReaderFromMediaSource", MFCreateSourceReaderFromMediaSource(device.get_media_source(), pAttributes, &device.mf_source_reader));
}
if (fourcc_map.count(fourcc)) fourcc = fourcc_map.at(fourcc);
for (DWORD j = 0; ; j++) {
com_ptr<IMFMediaType> media_type;
HRESULT hr = device.mf_source_reader->GetNativeMediaType((DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, j, &media_type);
if (hr == MF_E_NO_MORE_TYPES) break;
check("IMFSourceReader::GetNativeMediaType", hr);
UINT32 uvc_width, uvc_height, uvc_fps_num, uvc_fps_denom;
GUID subtype;
check("MFGetAttributeSize", MFGetAttributeSize(media_type, MF_MT_FRAME_SIZE, &uvc_width, &uvc_height));
if (uvc_width != width || uvc_height != height) continue;
check("IMFMediaType::GetGUID", media_type->GetGUID(MF_MT_SUBTYPE, &subtype));
if (subtype.Data1 != fourcc) continue;
check("MFGetAttributeRatio", MFGetAttributeRatio(media_type, MF_MT_FRAME_RATE, &uvc_fps_num, &uvc_fps_denom));
if (uvc_fps_denom == 0) continue;
//int uvc_fps = uvc_fps_num / uvc_fps_denom;
//LOG(INFO) << "uvc_fps: " << uvc_fps;
check("IMFSourceReader::SetCurrentMediaType", device.mf_source_reader->SetCurrentMediaType((DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, NULL, media_type));
device.callback = callback;
return;
}
throw_error() << "no matching media type for pixel format " << std::hex << fourcc;
}
void start_streaming(device &device, int num_transfer_bufs) {
device.start_streaming();
}
void stop_streaming(device &device) {
device.stop_streaming();
}
} // namespace uvc
MYNTEYE_END_NAMESPACE

109
src/mynteye/uvc/uvc.h Normal file
View File

@@ -0,0 +1,109 @@
// Copyright 2018 Slightech Co., Ltd. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MYNTEYE_UVC_UVC_H_
#define MYNTEYE_UVC_UVC_H_
#pragma once
#include <functional>
#include <memory>
#include <string>
#include <vector>
#include "mynteye/mynteye.h"
#include "mynteye/types.h"
#define MYNTEYE_VID 0x04B4
#define MYNTEYE_PID 0x00F9
MYNTEYE_BEGIN_NAMESPACE
namespace uvc {
typedef enum pu_query {
PU_QUERY_SET, // Set the value of a control
PU_QUERY_GET, // Get the value of a control
PU_QUERY_LAST
} pu_query;
struct MYNTEYE_API guid {
uint32_t data1;
uint16_t data2, data3;
uint8_t data4[8];
};
// Extension Unit
struct MYNTEYE_API xu {
uint8_t unit;
int node;
guid id;
};
typedef enum xu_query {
XU_QUERY_SET, // Set current value of the control
XU_QUERY_GET, // Get current value of the control
XU_QUERY_MIN, // Get min value of the control
XU_QUERY_MAX, // Get max value of the control
XU_QUERY_DEF, // Get default value of the control
XU_QUERY_LAST
} xu_query;
struct context; // Opaque type representing access to the underlying UVC
// implementation
struct device; // Opaque type representing access to a specific UVC device
// Enumerate devices
MYNTEYE_API std::shared_ptr<context> create_context();
MYNTEYE_API std::vector<std::shared_ptr<device>> query_devices(
std::shared_ptr<context> context);
// Static device properties
MYNTEYE_API std::string get_name(const device &device);
MYNTEYE_API int get_vendor_id(const device &device);
MYNTEYE_API int get_product_id(const device &device);
MYNTEYE_API std::string get_video_name(const device &device);
// Access PU (Processing Unit) controls
inline bool is_pu_control(Option option) {
return option >= Option::GAIN && option <= Option::CONTRAST;
}
MYNTEYE_API bool pu_control_range(
const device &device, Option option, int32_t *min, int32_t *max,
int32_t *def);
MYNTEYE_API bool pu_control_query(
const device &device, Option option, pu_query query, int32_t *value);
// Access XU (Extension Unit) controls
MYNTEYE_API bool xu_control_range(
const device &device, const xu &xu, uint8_t selector, uint8_t id,
int32_t *min, int32_t *max, int32_t *def);
MYNTEYE_API bool xu_control_query( // XU_QUERY_SET, XU_QUERY_GET
const device &device, const xu &xu, uint8_t selector, xu_query query,
uint16_t size, uint8_t *data);
// Control streaming
typedef std::function<void(const void *frame,
std::function<void()> continuation)> video_channel_callback;
MYNTEYE_API void set_device_mode(
device &device, int width, int height, int fourcc, int fps, // NOLINT
video_channel_callback callback);
MYNTEYE_API void start_streaming(device &device, int num_transfer_bufs); // NOLINT
MYNTEYE_API void stop_streaming(device &device); // NOLINT
} // namespace uvc
MYNTEYE_END_NAMESPACE
#endif // MYNTEYE_UVC_UVC_H_