diff --git a/gear-lib/libuvc/Makefile b/gear-lib/libuvc/Makefile index a45cc4e..56e0100 100644 --- a/gear-lib/libuvc/Makefile +++ b/gear-lib/libuvc/Makefile @@ -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 ############################################################################### diff --git a/gear-lib/libuvc/dummy.c b/gear-lib/libuvc/dummy.c new file mode 100644 index 0000000..b05a2ca --- /dev/null +++ b/gear-lib/libuvc/dummy.c @@ -0,0 +1,308 @@ +/****************************************************************************** + * Copyright (C) 2014-2020 Zhifeng Gong + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +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, ¬ify, 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, ¬ify, 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, ¬ify, 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, ¬ify, 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, +}; diff --git a/gear-lib/libuvc/libuvc.c b/gear-lib/libuvc/libuvc.c index f207a81..4cbbf6d 100644 --- a/gear-lib/libuvc/libuvc.c +++ b/gear-lib/libuvc/libuvc.c @@ -28,6 +28,7 @@ #include #include +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); diff --git a/gear-lib/libuvc/libuvc.h b/gear-lib/libuvc/libuvc.h index 011f868..bb58b96 100644 --- a/gear-lib/libuvc/libuvc.h +++ b/gear-lib/libuvc/libuvc.h @@ -31,12 +31,19 @@ #include #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); diff --git a/gear-lib/libuvc/test_libuvc.c b/gear-lib/libuvc/test_libuvc.c index 2900989..e39a798 100644 --- a/gear-lib/libuvc/test_libuvc.c +++ b/gear-lib/libuvc/test_libuvc.c @@ -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; } diff --git a/gear-lib/libuvc/v4l2.c b/gear-lib/libuvc/v4l2.c index 2e74a60..6a3cb3e 100644 --- a/gear-lib/libuvc/v4l2.c +++ b/gear-lib/libuvc/v4l2.c @@ -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);