875 lines
31 KiB
C++
875 lines
31 KiB
C++
/*
|
|
* Copyright (C) 2023 The Android Open Source Project
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
//#define LOG_NDEBUG 0
|
|
|
|
#include <jni.h>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include <glob.h>
|
|
#include <linux/usb/g_uvc.h>
|
|
#include <linux/usb/video.h>
|
|
#include <sys/epoll.h>
|
|
#include <sys/mman.h>
|
|
|
|
#include <DeviceAsWebcamNative.h>
|
|
#include <SdkFrameProvider.h>
|
|
#include <UVCProvider.h>
|
|
#include <Utils.h>
|
|
#include <log/log.h>
|
|
|
|
constexpr int MAX_EVENTS = 10;
|
|
constexpr uint32_t NUM_BUFFERS_ALLOC = 4;
|
|
constexpr uint32_t USB_PAYLOAD_TRANSFER_SIZE = 3072;
|
|
constexpr char kDeviceGlobPattern[] = "/dev/video*";
|
|
|
|
// Taken from UVC UAPI. The kernel handles mapping these back to actual USB interfaces set up by the
|
|
// UVC gadget function.
|
|
constexpr uint32_t CONTROL_INTERFACE_IDX = 0;
|
|
constexpr uint32_t STREAMING_INTERFACE_IDX = 1;
|
|
|
|
constexpr uint32_t FRAME_INTERVAL_NUM = 10'000'000;
|
|
|
|
namespace android {
|
|
namespace webcam {
|
|
|
|
Status EpollW::init() {
|
|
int fd = epoll_create1(EPOLL_CLOEXEC);
|
|
if (fd < 0) {
|
|
ALOGE("%s epoll_create failed: %s", __FUNCTION__, strerror(errno));
|
|
return Status::ERROR;
|
|
}
|
|
mEpollFd.reset(fd);
|
|
return Status::OK;
|
|
}
|
|
|
|
Status EpollW::add(int fd, uint32_t eventsIn) {
|
|
struct epoll_event event {};
|
|
event.data.fd = fd;
|
|
event.events = eventsIn;
|
|
if (epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, fd, &event) != 0) {
|
|
ALOGE("%s Epoll_ctl ADD failed %s \n", __FUNCTION__, strerror(errno));
|
|
return Status::ERROR;
|
|
}
|
|
return Status::OK;
|
|
}
|
|
|
|
Status EpollW::modify(int fd, uint32_t newEvents) {
|
|
struct epoll_event event {};
|
|
event.data.fd = fd;
|
|
event.events = newEvents;
|
|
// TODO(b/267794640): Could we use CTL_MOD instead with UVC, reliably.
|
|
if (epoll_ctl(mEpollFd.get(), EPOLL_CTL_DEL, fd, &event) != 0) {
|
|
ALOGE("%s Epoll_ctl DEL failed %s \n", __FUNCTION__, strerror(errno));
|
|
return Status::ERROR;
|
|
}
|
|
if (epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, fd, &event) != 0) {
|
|
ALOGE("%s Epoll_ctl ADD failed %s \n", __FUNCTION__, strerror(errno));
|
|
return Status::ERROR;
|
|
}
|
|
return Status::OK;
|
|
}
|
|
|
|
Status EpollW::remove(int fd) {
|
|
struct epoll_event event {};
|
|
if (epoll_ctl(mEpollFd.get(), EPOLL_CTL_DEL, fd, &event) != 0) {
|
|
ALOGE("%s Epoll_ctl DEL failed %s \n", __FUNCTION__, strerror(errno));
|
|
return Status::ERROR;
|
|
}
|
|
return Status::OK;
|
|
}
|
|
|
|
Events EpollW::waitForEvents() {
|
|
struct epoll_event events[MAX_EVENTS];
|
|
Events eventsVec;
|
|
int nFds = epoll_wait(mEpollFd.get(), events, MAX_EVENTS, /*msWait for 15 fps*/ 66);
|
|
|
|
if (nFds < 0) {
|
|
ALOGE("%s nFds was < 0 %s", __FUNCTION__, strerror(errno));
|
|
return eventsVec;
|
|
}
|
|
for (int i = 0; i < nFds; i++) {
|
|
eventsVec.emplace_back(events[i]);
|
|
}
|
|
return eventsVec;
|
|
}
|
|
|
|
Status UVCProvider::UVCDevice::openV4L2DeviceAndSubscribe(const std::string& videoNode) {
|
|
struct v4l2_capability cap {};
|
|
int fd = open(videoNode.c_str(), O_RDWR);
|
|
if (fd < 0) {
|
|
ALOGE("%s Couldn't open V4L2 device %s err: %d fd %d, err str %s", __FUNCTION__,
|
|
videoNode.c_str(), errno, fd, strerror(errno));
|
|
return Status::ERROR;
|
|
}
|
|
|
|
ALOGI("%s Start to listen to device fd %d", __FUNCTION__, fd);
|
|
mUVCFd.reset(fd);
|
|
memset(&cap, 0, sizeof(cap));
|
|
|
|
if (ioctl(mUVCFd.get(), VIDIOC_QUERYCAP, &cap) < 0) {
|
|
ALOGE("%s Couldn't get V4L2 device capabilities fd %d", __FUNCTION__, mUVCFd.get());
|
|
return Status::ERROR;
|
|
}
|
|
|
|
// Check that it is indeed a video output device
|
|
if (!(cap.capabilities & V4L2_CAP_VIDEO_OUTPUT)) {
|
|
ALOGE("%s V4L2 device capabilities don't contain video output, caps: %zu", __FUNCTION__,
|
|
(size_t)cap.capabilities);
|
|
return Status::ERROR;
|
|
}
|
|
|
|
mUVCProperties = parseUvcProperties();
|
|
|
|
// Subscribe to events
|
|
struct v4l2_event_subscription subscription {};
|
|
std::vector<unsigned long> events = {UVC_EVENT_SETUP, UVC_EVENT_DATA, UVC_EVENT_STREAMON,
|
|
UVC_EVENT_STREAMOFF, UVC_EVENT_DISCONNECT};
|
|
|
|
for (auto event : events) {
|
|
subscription.type = event;
|
|
if (ioctl(mUVCFd.get(), VIDIOC_SUBSCRIBE_EVENT, &subscription) < 0) {
|
|
ALOGE("%s Couldn't subscribe to V4L2 event %lu error %s", __FUNCTION__, event,
|
|
strerror(errno));
|
|
return Status::ERROR;
|
|
}
|
|
}
|
|
return Status::OK;
|
|
}
|
|
|
|
static bool isVideoOutputDevice(const char* dev) {
|
|
base::unique_fd fd(open(dev, O_RDWR));
|
|
if (fd.get() < 0) {
|
|
ALOGW("%s Opening V4L2 node %s failed %s", __FUNCTION__, dev, strerror(errno));
|
|
return false;
|
|
}
|
|
|
|
struct v4l2_capability capability {};
|
|
int ret = ioctl(fd.get(), VIDIOC_QUERYCAP, &capability);
|
|
if (ret < 0) {
|
|
ALOGV("%s v4l2 QUERYCAP %s failed %s", __FUNCTION__, dev, strerror(errno));
|
|
return false;
|
|
}
|
|
|
|
if (capability.device_caps & V4L2_CAP_VIDEO_OUTPUT) {
|
|
ALOGI("%s device %s supports VIDEO_OUTPUT", __FUNCTION__, dev);
|
|
return true;
|
|
}
|
|
ALOGV("%s device %s does not support VIDEO_OUTPUT", __FUNCTION__, dev);
|
|
return false;
|
|
}
|
|
|
|
void UVCProvider::UVCDevice::getFrameIntervals(ConfigFrame* frame, ConfigFormat* format) {
|
|
uint32_t index = 0;
|
|
while (true) {
|
|
struct v4l2_frmivalenum intervalDesc {};
|
|
intervalDesc.index = index;
|
|
intervalDesc.pixel_format = format->fcc;
|
|
intervalDesc.width = frame->width;
|
|
intervalDesc.height = frame->height;
|
|
int ret = ioctl(mUVCFd.get(), VIDIOC_ENUM_FRAMEINTERVALS, &intervalDesc);
|
|
if (ret != 0) {
|
|
return;
|
|
}
|
|
if (intervalDesc.index != index) {
|
|
ALOGE("%s V4L2 api returned different index %u from expected %u", __FUNCTION__,
|
|
intervalDesc.index, index);
|
|
}
|
|
uint32_t ival = 0;
|
|
switch (intervalDesc.type) {
|
|
case V4L2_FRMIVAL_TYPE_DISCRETE:
|
|
ival = (intervalDesc.discrete.numerator * FRAME_INTERVAL_NUM) /
|
|
intervalDesc.discrete.denominator;
|
|
break;
|
|
case V4L2_FRMIVAL_TYPE_STEPWISE:
|
|
// We only take the min here
|
|
ival = intervalDesc.stepwise.min.numerator / intervalDesc.stepwise.min.denominator;
|
|
break;
|
|
default:
|
|
ALOGE("%s frame type %u invalid", __FUNCTION__, intervalDesc.type);
|
|
break;
|
|
}
|
|
frame->intervals.emplace_back(ival);
|
|
index++;
|
|
}
|
|
}
|
|
|
|
void UVCProvider::UVCDevice::getFormatFrames(ConfigFormat* format) {
|
|
uint32_t index = 0;
|
|
while (true) {
|
|
struct v4l2_frmsizeenum frameDesc {};
|
|
frameDesc.index = index;
|
|
frameDesc.pixel_format = format->fcc;
|
|
ALOGI("Getting frames for format index %u", index);
|
|
int ret = ioctl(mUVCFd.get(), VIDIOC_ENUM_FRAMESIZES, &frameDesc);
|
|
if (ret != 0) {
|
|
return;
|
|
}
|
|
if (frameDesc.index != index) {
|
|
ALOGE("%s V4L2 api returned different index %u from expected %u", __FUNCTION__,
|
|
frameDesc.index, index);
|
|
}
|
|
ConfigFrame configFrame{};
|
|
configFrame.index = index;
|
|
switch (frameDesc.type) {
|
|
case V4L2_FRMSIZE_TYPE_DISCRETE:
|
|
configFrame.width = frameDesc.discrete.width;
|
|
configFrame.height = frameDesc.discrete.height;
|
|
break;
|
|
case V4L2_FRMSIZE_TYPE_STEPWISE:
|
|
// We only take the min here
|
|
configFrame.width = frameDesc.stepwise.min_width;
|
|
configFrame.height = frameDesc.stepwise.min_height;
|
|
break;
|
|
default:
|
|
ALOGE("%s frame type %u invalid", __FUNCTION__, frameDesc.type);
|
|
break;
|
|
}
|
|
getFrameIntervals(&configFrame, format);
|
|
format->frames.emplace_back(configFrame);
|
|
index++;
|
|
}
|
|
}
|
|
|
|
std::vector<ConfigFormat> UVCProvider::UVCDevice::getFormats() {
|
|
std::vector<ConfigFormat> retVal;
|
|
for (uint32_t index = 0; true; ++index) {
|
|
ALOGI("Getting formats for index %u", index);
|
|
struct v4l2_fmtdesc formatDesc {};
|
|
formatDesc.index = index;
|
|
formatDesc.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
|
|
int ret = ioctl(mUVCFd.get(), VIDIOC_ENUM_FMT, &formatDesc);
|
|
if (ret != 0) {
|
|
return retVal;
|
|
}
|
|
if (formatDesc.index != index) {
|
|
ALOGE("%s V4L2 api returned different index %u from expected %u", __FUNCTION__,
|
|
formatDesc.index, index);
|
|
}
|
|
|
|
ConfigFormat configFormat{};
|
|
configFormat.formatIndex = formatDesc.index; // TODO: Double check with jchowdhary@
|
|
configFormat.fcc = formatDesc.pixelformat;
|
|
getFormatFrames(&configFormat);
|
|
retVal.emplace_back(configFormat);
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<UVCProperties> UVCProvider::UVCDevice::parseUvcProperties() {
|
|
std::shared_ptr<UVCProperties> ret = std::make_shared<UVCProperties>();
|
|
ret->videoNode = mVideoNode;
|
|
ret->streaming.formats = getFormats();
|
|
ret->controlInterfaceNumber = CONTROL_INTERFACE_IDX;
|
|
ret->streaming.interfaceNumber = STREAMING_INTERFACE_IDX;
|
|
return ret;
|
|
}
|
|
|
|
UVCProvider::UVCDevice::UVCDevice(std::weak_ptr<UVCProvider> parent) {
|
|
mParent = std::move(parent);
|
|
|
|
// Initialize probe and commit controls with default values
|
|
FormatTriplet defaultFormatTriplet(/*formatIndex*/ 1, /*frameSizeIndex*/ 1,
|
|
/*frameInterval*/ 0);
|
|
|
|
mVideoNode = getVideoNode();
|
|
|
|
if (mVideoNode.empty()) {
|
|
ALOGE("%s: mVideoNode is empty, what ?", __FUNCTION__);
|
|
return;
|
|
}
|
|
|
|
if (openV4L2DeviceAndSubscribe(mVideoNode) != Status::OK) {
|
|
ALOGE("%s: Unable to open and subscribe to V4l2 node %s ?", __FUNCTION__,
|
|
mVideoNode.c_str());
|
|
return;
|
|
}
|
|
setStreamingControl(&mCommit, &defaultFormatTriplet);
|
|
mInited = true;
|
|
}
|
|
|
|
void UVCProvider::UVCDevice::closeUVCFd() {
|
|
if (mUVCFd.get() >= 0) {
|
|
struct v4l2_event_subscription subscription {};
|
|
subscription.type = V4L2_EVENT_ALL;
|
|
|
|
if (ioctl(mUVCFd.get(), VIDIOC_UNSUBSCRIBE_EVENT, subscription) < 0) {
|
|
ALOGE("%s Couldn't unsubscribe from V4L2 events error %s", __FUNCTION__,
|
|
strerror(errno));
|
|
}
|
|
}
|
|
mUVCFd.reset();
|
|
}
|
|
|
|
std::string UVCProvider::getVideoNode() {
|
|
std::string devNode;
|
|
ALOGV("%s start scanning for existing V4L2 OUTPUT devices", __FUNCTION__);
|
|
glob_t globRes;
|
|
glob(kDeviceGlobPattern, /*flags*/ 0, /*error_callback*/ nullptr, &globRes);
|
|
for (unsigned int i = 0; i < globRes.gl_pathc; ++i) {
|
|
ALOGV("%s file: %s", __FUNCTION__, globRes.gl_pathv[i]);
|
|
if (isVideoOutputDevice(globRes.gl_pathv[i])) {
|
|
devNode = globRes.gl_pathv[i];
|
|
break;
|
|
}
|
|
}
|
|
globfree(&globRes);
|
|
return devNode;
|
|
}
|
|
|
|
bool UVCProvider::UVCDevice::isInited() const {
|
|
return mInited;
|
|
}
|
|
|
|
void UVCProvider::UVCDevice::processSetupControlEvent(const struct usb_ctrlrequest* control,
|
|
struct uvc_request_data* resp) {
|
|
// TODO(b/267794640): Support control requests
|
|
resp->data[0] = 0x3;
|
|
resp->length = control->wLength;
|
|
}
|
|
|
|
void UVCProvider::UVCDevice::processSetupStreamingEvent(const struct usb_ctrlrequest* request,
|
|
struct uvc_request_data* response) {
|
|
uint8_t requestType = request->bRequest;
|
|
uint8_t controlSelect = request->wValue >> 8;
|
|
if (controlSelect != UVC_VS_PROBE_CONTROL && controlSelect != UVC_VS_COMMIT_CONTROL) {
|
|
ALOGE("%s: control select %u is invalid", __FUNCTION__, controlSelect);
|
|
return;
|
|
}
|
|
|
|
struct uvc_streaming_control* ctrl =
|
|
reinterpret_cast<struct uvc_streaming_control*>(response->data);
|
|
response->length = sizeof(struct uvc_streaming_control);
|
|
FormatTriplet maxFormatTriplet(/*formatIndex*/ UINT8_MAX,
|
|
/*frameSizeIndex*/ UINT8_MAX,
|
|
/*frameInterval*/ UINT32_MAX);
|
|
FormatTriplet defaultFormatTriplet(/*formatIndex*/ 1, /*frameSizeIndex*/ 1,
|
|
/*frameInterval*/ 0);
|
|
|
|
switch (requestType) {
|
|
case UVC_SET_CUR:
|
|
mCurrentControlState = controlSelect;
|
|
// UVC 1.5 spec section 4.3.1
|
|
response->length = 48;
|
|
break;
|
|
case UVC_GET_CUR:
|
|
if (controlSelect == UVC_VS_PROBE_CONTROL) {
|
|
memcpy(ctrl, &mProbe, sizeof(struct uvc_streaming_control));
|
|
} else {
|
|
memcpy(ctrl, &mCommit, sizeof(struct uvc_streaming_control));
|
|
}
|
|
break;
|
|
case UVC_GET_MAX:
|
|
setStreamingControl(ctrl, &maxFormatTriplet);
|
|
break;
|
|
case UVC_GET_MIN:
|
|
case UVC_GET_DEF:
|
|
setStreamingControl(ctrl, &defaultFormatTriplet);
|
|
break;
|
|
case UVC_GET_RES:
|
|
memset(ctrl, 0, sizeof(struct uvc_streaming_control));
|
|
break;
|
|
case UVC_GET_LEN:
|
|
response->data[0] = 0x00;
|
|
// UVC 1.5 spec section 4.3.1
|
|
response->data[0] = 0x30;
|
|
response->length = 2;
|
|
break;
|
|
case UVC_GET_INFO:
|
|
// UVC 1.5 Spec Section 4.1.2 "Get Request"
|
|
response->data[0] = 0x3;
|
|
response->length = 1;
|
|
break;
|
|
default:
|
|
ALOGE("%s requestType %u not supported", __FUNCTION__, requestType);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void UVCProvider::UVCDevice::processSetupClassEvent(const struct usb_ctrlrequest* request,
|
|
struct uvc_request_data* response) {
|
|
uint8_t interface = request->wIndex & 0xff;
|
|
ALOGV("%s: Interface %u", __FUNCTION__, interface);
|
|
|
|
if ((request->bRequestType & USB_RECIP_MASK) != USB_RECIP_INTERFACE) {
|
|
ALOGE("Invalid bRequestType byte %u", request->bRequestType);
|
|
return;
|
|
}
|
|
|
|
if (interface == mUVCProperties->controlInterfaceNumber) {
|
|
processSetupControlEvent(request, response);
|
|
} else if (interface == mUVCProperties->streaming.interfaceNumber) {
|
|
processSetupStreamingEvent(request, response);
|
|
}
|
|
}
|
|
|
|
void UVCProvider::UVCDevice::processSetupEvent(const struct usb_ctrlrequest* request,
|
|
struct uvc_request_data* response) {
|
|
uint8_t type = request->bRequestType;
|
|
uint8_t requestCode = request->bRequest;
|
|
uint16_t length = request->wLength;
|
|
uint16_t index = request->wIndex;
|
|
uint16_t value = request->wValue;
|
|
ALOGV("%s: type %u requestCode %u length %u index %u value %u", __FUNCTION__, type, requestCode,
|
|
length, index, value);
|
|
switch (type & USB_TYPE_MASK) {
|
|
case USB_TYPE_STANDARD:
|
|
ALOGW("USB_TYPE_STANDARD request not being handled");
|
|
break;
|
|
case USB_TYPE_CLASS:
|
|
processSetupClassEvent(request, response);
|
|
break;
|
|
default:
|
|
ALOGE("%s: Unknown request type %u", __FUNCTION__, type);
|
|
}
|
|
}
|
|
|
|
void UVCProvider::UVCDevice::setStreamingControl(struct uvc_streaming_control* streamingControl,
|
|
const FormatTriplet* req) {
|
|
// frame and format index start from 1.
|
|
uint8_t chosenFormatIndex = req->formatIndex > mUVCProperties->streaming.formats.size()
|
|
? mUVCProperties->streaming.formats.size()
|
|
: req->formatIndex;
|
|
const ConfigFormat& chosenFormat = mUVCProperties->streaming.formats[chosenFormatIndex - 1];
|
|
uint8_t chosenFrameIndex = req->frameSizeIndex > chosenFormat.frames.size()
|
|
? chosenFormat.frames.size()
|
|
: req->frameSizeIndex;
|
|
ALOGI("%s: chosenFrameIndex: %d", __FUNCTION__, chosenFrameIndex);
|
|
const ConfigFrame& chosenFrame = chosenFormat.frames[chosenFrameIndex - 1];
|
|
uint32_t reqFrameInterval = req->frameInterval;
|
|
bool largerFound = false;
|
|
// Choose the first frame interval >= requested. Frame intervals are expected to be in
|
|
// ascending order.
|
|
for (auto frameInterval : chosenFrame.intervals) {
|
|
if (reqFrameInterval <= frameInterval) {
|
|
reqFrameInterval = frameInterval;
|
|
largerFound = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!largerFound) {
|
|
reqFrameInterval = chosenFrame.intervals[chosenFrame.intervals.size() - 1];
|
|
}
|
|
memset(streamingControl, 0, sizeof(*streamingControl));
|
|
|
|
// TODO((b/267794640): Add documentation for magic numbers used here and elsewhere.
|
|
streamingControl->bmHint = 1;
|
|
streamingControl->bmFramingInfo = 3;
|
|
streamingControl->bPreferedVersion = 1;
|
|
streamingControl->bMaxVersion = 1;
|
|
|
|
streamingControl->bFormatIndex = chosenFormatIndex;
|
|
streamingControl->bFrameIndex = chosenFrameIndex;
|
|
streamingControl->dwFrameInterval = reqFrameInterval;
|
|
streamingControl->dwMaxPayloadTransferSize = USB_PAYLOAD_TRANSFER_SIZE;
|
|
switch (chosenFormat.fcc) {
|
|
case V4L2_PIX_FMT_YUYV:
|
|
case V4L2_PIX_FMT_MJPEG:
|
|
streamingControl->dwMaxVideoFrameSize = chosenFrame.width * chosenFrame.height * 2;
|
|
break;
|
|
default:
|
|
ALOGE("%s Video format is not YUYV or MJPEG ??", __FUNCTION__);
|
|
}
|
|
}
|
|
|
|
void UVCProvider::UVCDevice::commitControls() {
|
|
const ConfigFormat& commitFormat = mUVCProperties->streaming.formats[mCommit.bFormatIndex - 1];
|
|
const ConfigFrame& commitFrame = commitFormat.frames[mCommit.bFrameIndex - 1];
|
|
mFps = FRAME_INTERVAL_NUM / mCommit.dwFrameInterval;
|
|
|
|
memset(&mV4l2Format, 0, sizeof(mV4l2Format));
|
|
mV4l2Format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
|
|
mV4l2Format.fmt.pix.width = commitFrame.width;
|
|
mV4l2Format.fmt.pix.height = commitFrame.height;
|
|
mV4l2Format.fmt.pix.pixelformat = commitFormat.fcc;
|
|
mV4l2Format.fmt.pix.field = V4L2_FIELD_ANY;
|
|
mV4l2Format.fmt.pix.sizeimage = mCommit.dwMaxVideoFrameSize;
|
|
|
|
// Call ioctl VIDIOC_S_FMT which may change the fields in mV4l2Format
|
|
if (ioctl(mUVCFd.get(), VIDIOC_S_FMT, &mV4l2Format) < 0) {
|
|
ALOGE("%s Unable to set pixel format with the uvc gadget driver: %s", __FUNCTION__,
|
|
strerror(errno));
|
|
return;
|
|
}
|
|
ALOGV("%s controls committed frame width %u, height %u, format %u, sizeimage %u"
|
|
" frame rate %u mjpeg fourcc %u",
|
|
__FUNCTION__, mV4l2Format.fmt.pix.width, mV4l2Format.fmt.pix.height,
|
|
mV4l2Format.fmt.pix.pixelformat, mV4l2Format.fmt.pix.sizeimage, mFps, V4L2_PIX_FMT_MJPEG);
|
|
}
|
|
|
|
void UVCProvider::UVCDevice::processDataEvent(const struct uvc_request_data* data) {
|
|
const struct uvc_streaming_control* controlReq =
|
|
reinterpret_cast<const struct uvc_streaming_control*>(&data->data);
|
|
|
|
FormatTriplet triplet(controlReq->bFormatIndex, controlReq->bFrameIndex,
|
|
controlReq->dwFrameInterval);
|
|
|
|
switch (mCurrentControlState) {
|
|
case UVC_VS_PROBE_CONTROL:
|
|
setStreamingControl(&mProbe, &triplet);
|
|
break;
|
|
case UVC_VS_COMMIT_CONTROL:
|
|
setStreamingControl(&mCommit, &triplet);
|
|
commitControls();
|
|
break;
|
|
default:
|
|
ALOGE("mCurrentControlState is UNDEFINED");
|
|
break;
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<Buffer> UVCProvider::UVCDevice::mapBuffer(uint32_t i) {
|
|
struct v4l2_buffer buffer {};
|
|
|
|
buffer.index = i;
|
|
buffer.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
|
|
buffer.memory = V4L2_MEMORY_MMAP;
|
|
|
|
if (ioctl(mUVCFd.get(), VIDIOC_QUERYBUF, &buffer) < 0) {
|
|
ALOGE("%s: Unable to query V4L2 buffer index %u from gadget driver: %s", __FUNCTION__, i,
|
|
strerror(errno));
|
|
return nullptr;
|
|
}
|
|
|
|
void* mem = mmap(/*addr*/ nullptr, buffer.length, PROT_READ | PROT_WRITE, MAP_SHARED,
|
|
mUVCFd.get(), buffer.m.offset);
|
|
if (mem == MAP_FAILED) {
|
|
ALOGE("%s: Unable to map V4L2 buffer index %u from gadget driver: %s", __FUNCTION__, i,
|
|
strerror(errno));
|
|
return nullptr;
|
|
}
|
|
ALOGV("%s: Allocated and mapped buffer at %p of size %u", __FUNCTION__, mem, buffer.length);
|
|
return std::make_shared<V4L2Buffer>(mem, &buffer);
|
|
}
|
|
|
|
Status UVCProvider::UVCDevice::allocateAndMapBuffers(
|
|
std::shared_ptr<Buffer>* consumerBuffer,
|
|
std::vector<std::shared_ptr<Buffer>>* producerBuffers) {
|
|
if (consumerBuffer == nullptr || producerBuffers == nullptr) {
|
|
ALOGE("%s: ConsumerBuffer / producerBuffers are null", __FUNCTION__);
|
|
return Status::ERROR;
|
|
}
|
|
*consumerBuffer = nullptr;
|
|
producerBuffers->clear();
|
|
struct v4l2_requestbuffers requestBuffers {};
|
|
|
|
requestBuffers.count = NUM_BUFFERS_ALLOC;
|
|
requestBuffers.memory = V4L2_MEMORY_MMAP;
|
|
requestBuffers.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
|
|
|
|
// Request the driver to allocate buffers
|
|
if (ioctl(mUVCFd.get(), VIDIOC_REQBUFS, &requestBuffers) < 0) {
|
|
ALOGE("%s: Unable to request V4L2 buffers from gadget driver: %s", __FUNCTION__,
|
|
strerror(errno));
|
|
return Status::ERROR;
|
|
}
|
|
|
|
if (requestBuffers.count != NUM_BUFFERS_ALLOC) {
|
|
ALOGE("%s: Gadget driver could only allocate %u buffers instead of %u", __FUNCTION__,
|
|
requestBuffers.count, NUM_BUFFERS_ALLOC);
|
|
// TODO: We should probably be freeing buffers here
|
|
return Status::ERROR;
|
|
}
|
|
|
|
// First buffer is consumer buffer
|
|
*consumerBuffer = mapBuffer(0);
|
|
if (*consumerBuffer == nullptr) {
|
|
ALOGE("%s: Mapping consumer buffer failed", __FUNCTION__);
|
|
return Status::ERROR;
|
|
}
|
|
|
|
// The rest are producer buffers
|
|
for (uint32_t i = 1; i < NUM_BUFFERS_ALLOC; i++) {
|
|
std::shared_ptr<Buffer> buffer = mapBuffer(i);
|
|
if (buffer == nullptr) {
|
|
ALOGE("%s: Mapping producer buffer index %u failed", __FUNCTION__, i);
|
|
*consumerBuffer = nullptr;
|
|
producerBuffers->clear();
|
|
return Status::ERROR;
|
|
}
|
|
producerBuffers->push_back(buffer);
|
|
}
|
|
return Status::OK;
|
|
}
|
|
|
|
Status UVCProvider::UVCDevice::unmapBuffer(std::shared_ptr<Buffer>& buffer) {
|
|
if (buffer->getMem() != nullptr) {
|
|
if (munmap(buffer->getMem(), buffer->getLength()) < 0) {
|
|
ALOGE("%s: munmap failed for buffer with pointer %p", __FUNCTION__, buffer->getMem());
|
|
return Status::ERROR;
|
|
}
|
|
}
|
|
return Status::OK;
|
|
}
|
|
|
|
void UVCProvider::UVCDevice::destroyBuffers(std::shared_ptr<Buffer>& consumerBuffer,
|
|
std::vector<std::shared_ptr<Buffer>>& producerBuffers) {
|
|
if (unmapBuffer(consumerBuffer) != Status::OK) {
|
|
ALOGE("%s: Failed to unmap consumer buffer, continuing producer buffer cleanup anyway",
|
|
__FUNCTION__);
|
|
}
|
|
for (auto& buf : producerBuffers) {
|
|
if (unmapBuffer(buf) != Status::OK) {
|
|
ALOGE("%s: Failed to unmap producer buffer, continuing buffer cleanup anyway",
|
|
__FUNCTION__);
|
|
}
|
|
}
|
|
struct v4l2_requestbuffers zeroRequest {};
|
|
zeroRequest.count = 0;
|
|
zeroRequest.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
|
|
zeroRequest.memory = V4L2_MEMORY_MMAP;
|
|
|
|
if (ioctl(mUVCFd.get(), VIDIOC_REQBUFS, &zeroRequest) < 0) {
|
|
ALOGE("%s: request to free buffers from uvc gadget driver failed %s", __FUNCTION__,
|
|
strerror(errno));
|
|
}
|
|
}
|
|
|
|
Status UVCProvider::UVCDevice::getFrameAndQueueBufferToGadgetDriver(bool firstBuffer) {
|
|
// If first buffer, also call the STREAMON ioctl on uvc device
|
|
ALOGV("%s: E", __FUNCTION__);
|
|
if (firstBuffer) {
|
|
int type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
|
|
if (ioctl(mUVCFd.get(), VIDIOC_STREAMON, &type) < 0) {
|
|
ALOGE("%s: VIDIOC_STREAMON failed %s", __FUNCTION__, strerror(errno));
|
|
return Status::ERROR;
|
|
}
|
|
}
|
|
Buffer* buffer = mBufferManager->getFilledBufferAndSwap();
|
|
struct v4l2_buffer v4L2Buffer = *(static_cast<V4L2Buffer*>(buffer)->getV4L2Buffer());
|
|
ALOGV("%s: got buffer, queueing it with index %u", __FUNCTION__, v4L2Buffer.index);
|
|
|
|
if (ioctl(mUVCFd.get(), VIDIOC_QBUF, &v4L2Buffer) < 0) {
|
|
ALOGE("%s: VIDIOC_QBUF failed on gadget driver: %s", __FUNCTION__, strerror(errno));
|
|
return Status::ERROR;
|
|
}
|
|
ALOGV("%s: X", __FUNCTION__);
|
|
return Status::OK;
|
|
}
|
|
|
|
void UVCProvider::UVCDevice::processStreamOffEvent() {
|
|
int type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
|
|
if (ioctl(mUVCFd.get(), VIDIOC_STREAMOFF, &type) < 0) {
|
|
ALOGE("%s: uvc gadget driver request to switch stream off failed %s", __FUNCTION__,
|
|
strerror(errno));
|
|
return;
|
|
}
|
|
|
|
mFrameProvider.reset();
|
|
mBufferManager.reset();
|
|
memset(&mCommit, 0, sizeof(mCommit));
|
|
memset(&mProbe, 0, sizeof(mProbe));
|
|
memset(&mV4l2Format, 0, sizeof(mV4l2Format));
|
|
mFps = 0;
|
|
}
|
|
|
|
void UVCProvider::UVCDevice::processStreamOnEvent() {
|
|
// Allocate V4L2 and map buffers for circulation between camera and UVCDevice
|
|
mBufferManager = std::make_shared<BufferManager>(this);
|
|
CameraConfig config;
|
|
config.width = mV4l2Format.fmt.pix.width;
|
|
config.height = mV4l2Format.fmt.pix.height;
|
|
config.fcc = mV4l2Format.fmt.pix.pixelformat;
|
|
config.fps = mFps;
|
|
|
|
mFrameProvider = std::make_shared<SdkFrameProvider>(mBufferManager, config);
|
|
mFrameProvider->setStreamConfig();
|
|
mFrameProvider->startStreaming();
|
|
|
|
// Queue first buffer to start the stream
|
|
if (getFrameAndQueueBufferToGadgetDriver(true) != Status::OK) {
|
|
ALOGE("%s: Queueing first buffer to gadget driver failed, stream not started",
|
|
__FUNCTION__);
|
|
return;
|
|
}
|
|
auto parent = mParent.lock();
|
|
parent->watchStreamEvent();
|
|
}
|
|
|
|
UVCProvider::~UVCProvider() {
|
|
if (mUVCListenerThread.joinable()) {
|
|
mListenToUVCFds = false;
|
|
mUVCListenerThread.join();
|
|
}
|
|
|
|
if (mUVCDevice) {
|
|
int fd = mUVCDevice->getUVCFd();
|
|
if (fd >= 0) {
|
|
mEpollW.remove(fd);
|
|
}
|
|
}
|
|
mUVCDevice = nullptr;
|
|
}
|
|
|
|
Status UVCProvider::init() {
|
|
// TODO: What in the world?
|
|
return mEpollW.init();
|
|
}
|
|
|
|
void UVCProvider::UVCDevice::processStreamEvent() {
|
|
// Dequeue producer buffer
|
|
struct v4l2_buffer v4L2Buffer {};
|
|
v4L2Buffer.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
|
|
v4L2Buffer.memory = V4L2_MEMORY_MMAP;
|
|
|
|
if (ioctl(mUVCFd.get(), VIDIOC_DQBUF, &v4L2Buffer) < 0) {
|
|
ALOGE("%s: VIDIOC_DQBUF failed %s", __FUNCTION__, strerror(errno));
|
|
return;
|
|
}
|
|
// Get camera frame and queue it to gadget driver
|
|
if (getFrameAndQueueBufferToGadgetDriver() != Status::OK) {
|
|
return;
|
|
}
|
|
}
|
|
Status UVCProvider::UVCDevice::encodeImage(AHardwareBuffer* buffer, long timestamp) {
|
|
if (mFrameProvider == nullptr) {
|
|
ALOGE("%s: encodeImage called but there is no frame provider active", __FUNCTION__);
|
|
return Status::ERROR;
|
|
}
|
|
return mFrameProvider->encodeImage(buffer, timestamp);
|
|
}
|
|
|
|
void UVCProvider::processUVCEvent() {
|
|
struct v4l2_event event {};
|
|
int deviceFd = mUVCDevice->getUVCFd();
|
|
|
|
if (ioctl(deviceFd, VIDIOC_DQEVENT, &event) < 0) {
|
|
ALOGE("%s Failed to dequeue V4L2 event : %s", __FUNCTION__, strerror(errno));
|
|
return;
|
|
}
|
|
struct uvc_event* uvcEvent = reinterpret_cast<struct uvc_event*>(&event.u.data);
|
|
struct uvc_request_data uvcResponse {};
|
|
switch (event.type) {
|
|
case UVC_EVENT_CONNECT:
|
|
return;
|
|
case UVC_EVENT_DISCONNECT:
|
|
ALOGI("%s Disconnect event", __FUNCTION__);
|
|
stopService();
|
|
return;
|
|
case UVC_EVENT_SETUP:
|
|
ALOGV(" %s: Setup event ", __FUNCTION__);
|
|
mUVCDevice->processSetupEvent(&(uvcEvent->req), &uvcResponse);
|
|
break;
|
|
case UVC_EVENT_DATA:
|
|
ALOGV(" %s: Data event", __FUNCTION__);
|
|
mUVCDevice->processDataEvent(&(uvcEvent->data));
|
|
return;
|
|
case UVC_EVENT_STREAMON:
|
|
ALOGI("%s STREAMON event", __FUNCTION__);
|
|
mUVCDevice->processStreamOnEvent();
|
|
return;
|
|
case UVC_EVENT_STREAMOFF:
|
|
ALOGI("%s STREAMOFF event", __FUNCTION__);
|
|
mUVCDevice->processStreamOffEvent();
|
|
mEpollW.modify(mUVCDevice->getUVCFd(), EPOLLPRI);
|
|
return;
|
|
default:
|
|
ALOGI(" UVC Event unsupported %u", event.type);
|
|
break;
|
|
}
|
|
|
|
if (ioctl(deviceFd, UVCIOC_SEND_RESPONSE, &uvcResponse) < 0) {
|
|
ALOGE("%s Unable to send response to uvc gadget driver %s", __FUNCTION__, strerror(errno));
|
|
return;
|
|
}
|
|
}
|
|
|
|
// TODO: seems unnecessary
|
|
void UVCProvider::watchStreamEvent() {
|
|
mEpollW.modify(mUVCDevice->getUVCFd(), EPOLLPRI | EPOLLOUT);
|
|
}
|
|
|
|
void UVCProvider::ListenToUVCFds() {
|
|
// For UVC events
|
|
ALOGI("%s Start to listen to device fd %d", __FUNCTION__, mUVCDevice->getUVCFd());
|
|
mEpollW.add(mUVCDevice->getUVCFd(), EPOLLPRI);
|
|
// For stream events : dequeue and queue buffers
|
|
while (mListenToUVCFds) {
|
|
Events events = mEpollW.waitForEvents();
|
|
for (auto event : events) {
|
|
// Priority event might come with regular events, so one event could have both
|
|
if (event.events & EPOLLPRI) {
|
|
processUVCEvent();
|
|
}
|
|
|
|
if (event.events & EPOLLOUT) {
|
|
if (mUVCDevice != nullptr) {
|
|
mUVCDevice->processStreamEvent();
|
|
} else {
|
|
ALOGW("mUVCDevice is null we've disconnected");
|
|
}
|
|
} else {
|
|
ALOGW("Which event fd is %d ? event %u", event.data.fd, event.events);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int UVCProvider::encodeImage(AHardwareBuffer* buffer, long timestamp) {
|
|
if (mUVCDevice == nullptr) {
|
|
ALOGE("%s: Request to encode Image without UVCDevice Running.", __FUNCTION__);
|
|
return -1;
|
|
}
|
|
return mUVCDevice->encodeImage(buffer, timestamp) == Status::OK ? 0 : -1;
|
|
}
|
|
|
|
void UVCProvider::startUVCListenerThread() {
|
|
mListenToUVCFds = true;
|
|
mUVCListenerThread =
|
|
DeviceAsWebcamNative::createJniAttachedThread(&UVCProvider::ListenToUVCFds, this);
|
|
ALOGI("Started new UVCListenerThread");
|
|
}
|
|
|
|
void UVCProvider::stopAndWaitForUVCListenerThread() {
|
|
// This thread stays alive till onDestroy is called.
|
|
if (mUVCListenerThread.joinable()) {
|
|
mListenToUVCFds = false;
|
|
mUVCListenerThread.join();
|
|
}
|
|
}
|
|
|
|
Status UVCProvider::startService() {
|
|
// Resets old state for epoll since this is a new start for the service.
|
|
mEpollW.init();
|
|
if (mUVCDevice != nullptr) {
|
|
mUVCDevice->closeUVCFd();
|
|
}
|
|
mUVCDevice = std::make_shared<UVCDevice>(shared_from_this());
|
|
if (!mUVCDevice->isInited()) {
|
|
return Status::ERROR;
|
|
}
|
|
stopAndWaitForUVCListenerThread(); // Just in case it is already running
|
|
startUVCListenerThread();
|
|
return Status::OK;
|
|
}
|
|
|
|
void UVCProvider::stopService() {
|
|
// TODO: Try removing this
|
|
mUVCDevice->processStreamOffEvent();
|
|
mEpollW.remove(mUVCDevice->getUVCFd());
|
|
// Signal the service to stop.
|
|
// UVC Provider will get destructed when the Java Service is destroyed.
|
|
DeviceAsWebcamServiceManager::kInstance->stopService();
|
|
mUVCDevice = nullptr;
|
|
mListenToUVCFds = false;
|
|
}
|
|
|
|
} // namespace webcam
|
|
} // namespace android
|