[libuvc] add dummy support for fake file

This commit is contained in:
gozfree
2020-11-23 02:06:35 +08:00
parent d69b55bb9f
commit c3315ef46d
6 changed files with 376 additions and 16 deletions

View File

@@ -30,7 +30,7 @@ TGT_LIB_SO = $(LIBNAME).so
TGT_LIB_SO_VER = $(TGT_LIB_SO).${VER}
TGT_UNIT_TEST = test_$(LIBNAME)
OBJS_LIB = $(LIBNAME).o v4l2.o
OBJS_LIB = $(LIBNAME).o v4l2.o dummy.o
OBJS_UNIT_TEST = test_$(LIBNAME).o
###############################################################################

308
gear-lib/libuvc/dummy.c Normal file
View File

@@ -0,0 +1,308 @@
/******************************************************************************
* Copyright (C) 2014-2020 Zhifeng Gong <gozfree@163.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
******************************************************************************/
#include "libuvc.h"
#include <libthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <time.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/epoll.h>
struct dummy_ctx {
int fd;
int rd_fd;
int wr_fd;
uint64_t seek_offset;
uint64_t first_ts;
uint64_t frame_id;
struct uvc_ctx *parent;
struct thread *thread;
bool is_streaming;
int epfd;
struct epoll_event events;
};
#define timespec2ns(ts) \
(((uint64_t)ts.tv_sec * 1000000000) + ((uint64_t)ts.tv_nsec))
static void *uvc_dummy_open(struct uvc_ctx *uvc, const char *dev, struct uvc_config *conf)
{
int fds[2];
struct dummy_ctx *c = calloc(1, sizeof(struct dummy_ctx));
if (!c) {
printf("malloc dummy_ctx failed!\n");
return NULL;
}
c->fd = open(dev, O_RDWR);
if (c->fd == -1) {
printf("open %s failed: %d\n", dev, errno);
goto failed;
}
c->frame_id = 0;
c->seek_offset = 0;
if (pipe(fds)) {
printf("create pipe failed(%d): %s\n", errno, strerror(errno));
goto failed;
}
c->rd_fd = fds[0];
c->wr_fd = fds[1];
memcpy(&uvc->conf, conf, sizeof(struct uvc_config));
c->parent = uvc;
return c;
failed:
if (c->fd != -1) {
close(c->fd);
}
if (c) {
free(c);
}
return NULL;
}
static int msleep(uint64_t ms)
{
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = ms*1000;
return select(0, NULL, NULL, NULL, &tv);
}
static int uvc_dummy_enqueue(struct uvc_ctx *uvc, void *buf, size_t len)
{
char notify = '1';
uint64_t delay_ms = 0;
struct dummy_ctx *c = (struct dummy_ctx *)uvc->opaque;
delay_ms = (1000)/(uvc->conf.fps.num/(uvc->conf.fps.den*1.0));
msleep(delay_ms);
if (write(c->wr_fd, &notify, 1) != 1) {
printf("Failed writing to notify pipe\n");
return -1;
}
return 0;
}
static int uvc_dummy_dequeue(struct uvc_ctx *uvc, struct video_frame *frame)
{
int i;
ssize_t ret;
size_t len;
char notify;
struct timespec ts;
struct dummy_ctx *c = (struct dummy_ctx *)uvc->opaque;
if (read(c->rd_fd, &notify, sizeof(notify)) != 1) {
printf("Failed read from notify pipe\n");
return -1;
}
for (i = 0; i < frame->planes; ++i) {
len = frame->linesize[i]*frame->height;
ret = read(c->fd, frame->data[i], len);
if (ret != len) {
printf("read failed: ret=%lu, len=%zu, errno=%d, ptr=%p\n", ret, len, errno, frame->data[i]);
return -1;
}
c->seek_offset += len;
lseek(c->fd, c->seek_offset, SEEK_SET);
}
if (-1 == clock_gettime(CLOCK_REALTIME, &ts)) {
printf("clock_gettime failed %d:%s\n", errno, strerror(errno));
return -1;
}
frame->timestamp = timespec2ns(ts);
if (c->frame_id == 0) {
c->first_ts = frame->timestamp;
}
frame->timestamp -= c->first_ts;
frame->frame_id = c->frame_id;
c->frame_id++;
return frame->total_size;
}
static int uvc_dummy_poll_init(struct dummy_ctx *c)
{
struct epoll_event epev;
memset(&epev, 0, sizeof(epev));
printf("dummy_ctx=%p\n", c);
c->epfd = epoll_create(1);
if (c->epfd == -1) {
printf("epoll_create failed %d!\n", errno);
return -1;
}
epev.events = EPOLLIN | EPOLLOUT | EPOLLPRI | EPOLLET | EPOLLERR;
epev.data.ptr = (void *)c;
if (-1 == epoll_ctl(c->epfd, EPOLL_CTL_ADD, c->rd_fd, &epev)) {
printf("epoll_ctl EPOLL_CTL_ADD failed %d!\n", errno);
return -1;
}
return 0;
}
static int uvc_dummy_poll(struct uvc_ctx *uvc, int timeout)
{
struct dummy_ctx *c = (struct dummy_ctx *)uvc->opaque;
int ret = 0;
int n = epoll_wait(c->epfd, &c->events, 1, timeout);
switch (n) {
case 0:
printf("poll timeout\n");
ret = -1;
break;
case -1:
printf("poll error %d\n", errno);
ret = -1;
break;
default:
ret = 0;
break;
}
return ret;
}
static void uvc_dummy_poll_deinit(struct dummy_ctx *c)
{
if (-1 == epoll_ctl(c->epfd, EPOLL_CTL_DEL, c->rd_fd, NULL)) {
printf("epoll_ctl EPOLL_CTL_DEL failed %d!\n", errno);
}
close(c->epfd);
}
static void *dummy_thread(struct thread *t, void *arg)
{
struct uvc_ctx *uvc = arg;
struct dummy_ctx *c = (struct dummy_ctx *)uvc->opaque;
struct video_frame frame;
if (uvc_dummy_poll_init(c) == -1) {
printf("uvc_dummy_poll_init failed!\n");
}
video_frame_init(&frame, uvc->conf.format, uvc->conf.width, uvc->conf.height, VFC_ALLOC);
c->is_streaming = true;
while (c->is_streaming) {
if (uvc_dummy_enqueue(uvc, NULL, 0) != 0) {
printf("uvc_dummy_enqueue failed\n");
continue;
}
if (uvc_dummy_poll(uvc, -1) != 0) {
printf("uvc_dummy_poll failed\n");
continue;
}
if (uvc_dummy_dequeue(uvc, &frame) == -1) {
printf("uvc_dummy_dequeue failed\n");
}
uvc->on_video_frame(uvc, &frame);
}
uvc_dummy_poll_deinit(c);
return NULL;
}
static int uvc_dummy_start_stream(struct uvc_ctx *uvc)
{
char notify = '1';
struct dummy_ctx *c = (struct dummy_ctx *)uvc->opaque;
if (c->is_streaming) {
printf("dummy is streaming already!\n");
return -1;
}
if (write(c->wr_fd, &notify, 1) != 1) {
printf("Failed writing to notify pipe\n");
return -1;
}
if (uvc->on_video_frame) {
c->thread = thread_create(dummy_thread, uvc);
}
return 0;
}
static int uvc_dummy_stop_stream(struct uvc_ctx *uvc)
{
struct dummy_ctx *c = (struct dummy_ctx *)uvc->opaque;
char notify = '1';
if (!c->is_streaming) {
printf("dummy stream stopped already!\n");
return -1;
}
if (write(c->wr_fd, &notify, 1) != 1) {
printf("Failed writing to notify pipe\n");
return -1;
}
if (uvc->on_video_frame) {
c->is_streaming = false;
thread_destroy(c->thread);
}
return 0;
}
static int uvc_dummy_query_frame(struct uvc_ctx *uvc, struct video_frame *frame)
{
if (uvc_dummy_enqueue(uvc, NULL, 0) != 0) {
printf("uvc_dummy_enqueue failed\n");
return -1;
}
if (uvc_dummy_dequeue(uvc, frame) == -1) {
printf("uvc_dummy_dequeue failed\n");
return -1;
}
return 0;
}
static void uvc_dummy_close(struct uvc_ctx *uvc)
{
struct dummy_ctx *c = (struct dummy_ctx *)uvc->opaque;
uvc_dummy_stop_stream(uvc);
close(c->fd);
close(c->epfd);
close(c->rd_fd);
close(c->wr_fd);
free(c);
}
struct uvc_ops dummy_ops = {
.open = uvc_dummy_open,
.close = uvc_dummy_close,
.ioctl = NULL,
.start_stream = uvc_dummy_start_stream,
.stop_stream = uvc_dummy_stop_stream,
.query_frame = uvc_dummy_query_frame,
};

View File

@@ -28,6 +28,7 @@
#include <errno.h>
#include <stdarg.h>
extern struct uvc_ops dummy_ops;
#if defined (OS_LINUX)
extern struct uvc_ops v4l2_ops;
#elif defined (OS_WINDOWS)
@@ -35,15 +36,16 @@ extern struct uvc_ops dshow_ops;
#endif
static struct uvc_ops *uvc_ops[] = {
[UVC_TYPE_DUMMY] = &dummy_ops,
#if defined (OS_LINUX)
&v4l2_ops,
[UVC_TYPE_V4L2] = &v4l2_ops,
#elif defined (OS_WINDOWS)
&dshow_ops,
[UVC_TYPE_DSHOW] = &dshow_ops,
#endif
NULL
[UVC_TYPE_MAX] = NULL,
};
struct uvc_ctx *uvc_open(const char *dev, struct uvc_config *conf)
struct uvc_ctx *uvc_open(enum uvc_type type, const char *dev, struct uvc_config *conf)
{
struct uvc_ctx *uvc;
if (!dev || !conf) {
@@ -55,7 +57,11 @@ struct uvc_ctx *uvc_open(const char *dev, struct uvc_config *conf)
printf("malloc failed!\n");
return NULL;
}
uvc->ops = uvc_ops[0];
uvc->ops = uvc_ops[type];
if (!uvc->ops) {
printf("uvc->ops %d is NULL!\n", type);
return NULL;
}
uvc->opaque = uvc->ops->open(uvc, dev, conf);
if (!uvc->opaque) {
printf("open %s failed!\n", dev);

View File

@@ -31,12 +31,19 @@
#include <sys/ioctl.h>
#endif
#define LIBUVC_VERSION "0.1.0"
#define LIBUVC_VERSION "0.2.0"
#ifdef __cplusplus
extern "C" {
#endif
enum uvc_type {
UVC_TYPE_DUMMY = 0,
UVC_TYPE_V4L2,
UVC_TYPE_DSHOW,
UVC_TYPE_MAX,
};
struct uvc_ctx;
struct uvc_ops;
typedef int (video_frame_cb)(struct uvc_ctx *c, struct video_frame *frame);
@@ -78,7 +85,7 @@ struct uvc_ops {
int (*query_frame)(struct uvc_ctx *c, struct video_frame *frame);
};
struct uvc_ctx *uvc_open(const char *dev, struct uvc_config *conf);
struct uvc_ctx *uvc_open(enum uvc_type type, const char *dev, struct uvc_config *conf);
int uvc_ioctl(struct uvc_ctx *c, unsigned long int cmd, ...);
void uvc_close(struct uvc_ctx *c);

View File

@@ -31,7 +31,8 @@
#define VIDEO_DEV "/dev/video0"
#define VIDEO_WIDTH 640
#define VIDEO_HEIGHT 480
#define OUTPUT_FILE "uvc.yuv"
#define OUTPUT_V4L2 "v4l2.yuv"
#define OUTPUT_DUMMY "dummy.yuv"
static struct file *fp;
@@ -42,7 +43,7 @@ static int on_frame(struct uvc_ctx *c, struct video_frame *frm)
return 0;
}
int main(int argc, char **argv)
int v4l2_test()
{
struct video_frame *frm;
struct uvc_config conf = {
@@ -50,7 +51,7 @@ int main(int argc, char **argv)
.height = VIDEO_HEIGHT,
.fps = {30, 1},
};
struct uvc_ctx *uvc = uvc_open(VIDEO_DEV, &conf);
struct uvc_ctx *uvc = uvc_open(UVC_TYPE_V4L2, VIDEO_DEV, &conf);
if (!uvc) {
printf("uvc_open failed!\n");
return -1;
@@ -64,13 +65,54 @@ int main(int argc, char **argv)
printf("%s %dx%d@%d/%d fps format:%s\n", VIDEO_DEV, uvc->conf.width, uvc->conf.height,
uvc->conf.fps.num, uvc->conf.fps.den, pixel_format_to_string(uvc->conf.format));
//uvc_ioctl(uvc, UVC_GET_CAP, NULL, 0);
fp = file_open(OUTPUT_FILE, F_CREATE);
fp = file_open(OUTPUT_V4L2, F_CREATE);
uvc_start_stream(uvc, on_frame);
sleep(5);
uvc_stop_stream(uvc);
file_close(fp);
video_frame_destroy(frm);
uvc_close(uvc);
printf("write %s fininshed!\n", OUTPUT_FILE);
printf("write %s fininshed!\n", OUTPUT_V4L2);
return 0;
}
int dummy_test()
{
struct video_frame *frm;
struct uvc_config conf = {
.width = 320,
.height = 240,
.fps = {5, 1},
.format = PIXEL_FORMAT_YUY2,
};
struct uvc_ctx *uvc = uvc_open(UVC_TYPE_DUMMY, "sample_yuv422p.yuv", &conf);
if (!uvc) {
printf("uvc_open failed!\n");
return -1;
}
frm = video_frame_create(uvc->conf.format, uvc->conf.width, uvc->conf.height, VFC_NONE);
if (!frm) {
printf("video_frame_create failed!\n");
uvc_close(uvc);
return -1;
}
printf("%s %dx%d@%d/%d fps format:%s\n", VIDEO_DEV, uvc->conf.width, uvc->conf.height,
uvc->conf.fps.num, uvc->conf.fps.den, pixel_format_to_string(uvc->conf.format));
fp = file_open(OUTPUT_DUMMY, F_CREATE);
uvc_start_stream(uvc, on_frame);
sleep(5);
uvc_stop_stream(uvc);
file_sync(fp);
file_close(fp);
video_frame_destroy(frm);
uvc_close(uvc);
printf("write %s fininshed!\n", OUTPUT_DUMMY);
return 0;
}
int main(int argc, char **argv)
{
v4l2_test();
dummy_test();
return 0;
}

View File

@@ -92,7 +92,6 @@ struct v4l2_ctx {
int req_count;
bool qbuf_done;
uint64_t first_ts;
uint64_t prev_ts;
uint64_t frame_id;
struct v4l2_capability cap;
uint32_t ctrl_flags;
@@ -463,7 +462,6 @@ retry:
frame->frame_id = c->frame_id;
c->frame_id++;
c->prev_ts = frame->timestamp;
start = (uint8_t *)c->buf[qbuf.index].iov_base;
if (frame->flag == VFC_NONE) {//frame data ptr
@@ -492,7 +490,6 @@ static int uvc_v4l2_poll_init(struct v4l2_ctx *c)
}
epev.events = EPOLLIN | EPOLLOUT | EPOLLPRI | EPOLLET | EPOLLERR;
epev.data.fd = c->fd;
epev.data.ptr = (void *)c;
if (-1 == epoll_ctl(c->epfd, EPOLL_CTL_ADD, c->fd, &epev)) {
printf("epoll_ctl EPOLL_CTL_ADD failed %d!\n", errno);