Add vision_source_v4l2_{start,stop}_stream 04/278904/3 submit/tizen/20220801.025913
authorKwanghoon Son <k.son@samsung.com>
Thu, 28 Jul 2022 00:21:20 +0000 (20:21 -0400)
committerKwanghoon Son <k.son@samsung.com>
Thu, 28 Jul 2022 02:51:44 +0000 (22:51 -0400)
[Version] : 0.0.5
[Issue type] New function

Add v4l2 backend start, stop stream function

Change-Id: I2fe0565fdf8347eee5528ab0d37403d69d4c7504
Signed-off-by: Kwanghoon Son <k.son@samsung.com>
packaging/vision-source-v4l2.spec
src/vision_source_v4l2.c
src/vision_source_v4l2_private.h
test/test_vision_source_v4l2.cpp

index 8619d8714bb04ffd25ad406a22f61108cf8201b4..9b5fd3c4ca70c5a59ca082b31f4a9e66a67bcc9c 100644 (file)
@@ -2,7 +2,7 @@
 
 Name:        vision-source-v4l2
 Summary:     vision source-v4l2
-Version:     0.0.4
+Version:     0.0.5
 Release:     0
 Group:       Multimedia/Framework
 License:     Apache-2.0
index 35d52214fad8ca329105a3d60f81b1348a499625..b5bb70e5ecb2579d5e46a1e3a6ea8375a994070a 100644 (file)
@@ -115,6 +115,36 @@ static void __vision_source_get_fps_list(int device_fd, guint32 pixel_format,
        fps_list->count = ival.index;
 }
 
+static int __vision_source_v4l2_qbuf(int device_fd, int type, int memory,
+                                                                        int index)
+{
+       struct v4l2_buffer v4l2_buf;
+       struct v4l2_plane v4l2_planes[V4L2_PLANES_MAX];
+
+       if (device_fd < 0) {
+               LOGE("invalid fd %d", device_fd);
+               return VISION_SOURCE_ERROR_INVALID_PARAMETER;
+       }
+
+       memset(&v4l2_buf, 0x0, sizeof(struct v4l2_buffer));
+       memset(v4l2_planes, 0x0, sizeof(v4l2_planes));
+
+       v4l2_buf.index = index;
+       v4l2_buf.type = type;
+       v4l2_buf.memory = memory;
+       v4l2_buf.m.planes = v4l2_planes;
+
+       if (v4l2_ioctl(device_fd, VIDIOC_QBUF, &v4l2_buf) < 0) {
+               LOGE("qbuf failed.  [i: %d, t: %d, m: %d] errno %d", index, type,
+                        memory, errno);
+               return VISION_SOURCE_ERROR_INTERNAL;
+       }
+
+       /*LOGD("QBUF done [i: %d, t: %d, m: %d]", index, type, memory);*/
+
+       return VISION_SOURCE_ERROR_NONE;
+}
+
 static int
 __vision_source_get_device_info(int device_index, int device_fd,
                                                                vision_source_device_info_s *device_info,
@@ -340,6 +370,7 @@ static int __vision_source_get_device_info_list(
 
 _GET_DEVICE_INFO_LIST_DONE:
        LOGD("ret 0x%x", ret);
+       globfree(&glob_buf);
 
        if (ret != VISION_SOURCE_ERROR_NONE)
                g_free(device_info_list);
@@ -539,6 +570,170 @@ static int __vision_source_set_stream(vision_source_v4l2_s *handle,
        return VISION_SOURCE_ERROR_NONE;
 }
 
+static int __vision_source_v4l2_dqbuf(int device_fd, int type, int memory,
+                                                                         int *index, unsigned int *used_size)
+{
+       int ret = VISION_SOURCE_ERROR_NONE;
+       struct v4l2_buffer v4l2_buf;
+       struct v4l2_plane v4l2_planes[V4L2_PLANES_MAX];
+
+       if (device_fd < 0) {
+               LOGE("invalid fd %d", device_fd);
+               return VISION_SOURCE_ERROR_INVALID_PARAMETER;
+       }
+
+       if (!index) {
+               LOGE("NULL parameter");
+               return VISION_SOURCE_ERROR_INVALID_PARAMETER;
+       }
+
+       memset(&v4l2_buf, 0x0, sizeof(struct v4l2_buffer));
+       memset(v4l2_planes, 0x0, sizeof(v4l2_planes));
+
+       v4l2_buf.type = type;
+       v4l2_buf.memory = memory;
+       v4l2_buf.m.planes = v4l2_planes;
+
+       ret = v4l2_ioctl(device_fd, VIDIOC_DQBUF, &v4l2_buf);
+       if (ret < 0) {
+               if (errno != EIO) {
+                       LOGE("dqbuf failed. [t: %d, m: %d] errno %d", type, memory, errno);
+                       return VISION_SOURCE_ERROR_INTERNAL;
+               }
+       }
+
+       *index = v4l2_buf.index;
+       *used_size = v4l2_buf.bytesused;
+
+       return VISION_SOURCE_ERROR_NONE;
+}
+
+static int __vision_source_v4l2_wait_frame(int device_fd, int wait_time)
+{
+       int ret = VISION_SOURCE_ERROR_NONE;
+       fd_set fds;
+       struct timeval timeout;
+
+       if (device_fd < 0) {
+               LOGE("invalid fd %d", device_fd);
+               return VISION_SOURCE_ERROR_INVALID_PARAMETER;
+       }
+
+       FD_ZERO(&fds);
+       FD_SET(device_fd, &fds);
+
+       memset(&timeout, 0x0, sizeof(struct timeval));
+
+       timeout.tv_sec = wait_time;
+       timeout.tv_usec = 0;
+
+       /*LOGD("select : %d sec", wait_time);*/
+
+       ret = select(device_fd + 1, &fds, NULL, NULL, &timeout);
+       if (ret == -1) {
+               if (EINTR == errno) {
+                       LOGD("select error : EINTR");
+                       return VISION_SOURCE_ERROR_NONE;
+               }
+               LOGE("select failed. errno %d", errno);
+               return VISION_SOURCE_ERROR_INTERNAL;
+       }
+
+       if (ret == 0) {
+               LOGE("select timeout.");
+               return VISION_SOURCE_ERROR_INTERNAL;
+       }
+
+       /*LOGD("select done");*/
+
+       return VISION_SOURCE_ERROR_NONE;
+}
+
+static int
+__vision_source_start_stream(vision_source_v4l2_s *handle,
+                                                        vision_source_pixel_format_e pixel_format,
+                                                        vision_source_resolution_s *resolution,
+                                                        uint32_t fps, uint32_t request_buffer_count)
+{
+       vision_source_buffer_s *buffer = NULL;
+       struct v4l2_buffer v4l2_buf;
+       struct v4l2_plane v4l2_planes[V4L2_PLANES_MAX];
+       guint32 plane_num = 0;
+
+       int ret = __vision_source_set_stream(handle, pixel_format, resolution, fps,
+                                                                                request_buffer_count);
+       if (ret != VISION_SOURCE_ERROR_NONE) {
+               return ret;
+       }
+
+       /* request buffer */
+       ret = __vision_source_v4l2_reqbufs(handle->device_fd, handle->buffer_type,
+                                                                          V4L2_MEMORY_MMAP, request_buffer_count,
+                                                                          &handle->buffer_count);
+       if (ret != VISION_SOURCE_ERROR_NONE) {
+               return ret;
+       }
+
+       LOGD("buffer count : request %d -> result %d", request_buffer_count,
+                handle->buffer_count);
+
+       /* query buffer, mmap and qbuf */
+       for (int i = 0; i < handle->buffer_count; i++) {
+               memset(&v4l2_buf, 0x0, sizeof(struct v4l2_buffer));
+               memset(v4l2_planes, 0x0, sizeof(v4l2_planes));
+
+               v4l2_buf.type = handle->buffer_type;
+               v4l2_buf.memory = V4L2_MEMORY_MMAP;
+               v4l2_buf.index = i;
+               v4l2_buf.m.planes = v4l2_planes;
+               v4l2_buf.length = plane_num;
+
+               if (v4l2_ioctl(handle->device_fd, VIDIOC_QUERYBUF, &v4l2_buf) < 0) {
+                       LOGE("[%d] query buf failed. errno %d", i, errno);
+                       goto _START_STREAM_FAILED;
+               }
+
+               buffer = &handle->vision_source_buffers[i];
+
+               buffer->index = i;
+               buffer->pixel_format = pixel_format;
+               buffer->resolution.width = resolution->width;
+               buffer->resolution.height = resolution->height;
+               buffer->total_size = v4l2_buf.length;
+               buffer->num_planes = plane_num;
+               buffer->planes[0].size = v4l2_buf.length;
+               buffer->planes[0].data =
+                               v4l2_mmap(0, v4l2_buf.length, PROT_READ | PROT_WRITE,
+                                                 MAP_SHARED, handle->device_fd, v4l2_buf.m.offset);
+
+               if (buffer->planes[0].data == MAP_FAILED) {
+                       LOGE("[%d] mmap failed (errno %d)", i, errno);
+                       goto _START_STREAM_FAILED;
+               }
+
+               if (__vision_source_v4l2_qbuf(handle->device_fd, handle->buffer_type,
+                                                                         V4L2_MEMORY_MMAP,
+                                                                         i) != VISION_SOURCE_ERROR_NONE) {
+                       LOGE("[%d] qbuf failed (errno %d)", i, errno);
+                       goto _START_STREAM_FAILED;
+               }
+       }
+
+       /* stream on */
+       ret = __vision_source_v4l2_stream(handle->device_fd, handle->buffer_type,
+                                                                         TRUE);
+       if (ret != VISION_SOURCE_ERROR_NONE) {
+               LOGE("stream on failed");
+               goto _START_STREAM_FAILED;
+       }
+
+       return VISION_SOURCE_ERROR_NONE;
+
+_START_STREAM_FAILED:
+       __vision_source_stop_stream(handle, handle->buffer_count);
+       return ret;
+}
+
 int vision_source_v4l2_init(vision_source_h *handle)
 {
        LOGD("enter");
@@ -779,6 +974,135 @@ int vision_source_v4l2_set_stream_format(vision_source_h handle,
        return VISION_SOURCE_ERROR_NONE;
 }
 
+static void *__fetch_buffer_and_callback(gpointer data)
+{
+       vision_source_v4l2_s *v4l2_handle = (vision_source_v4l2_s *) data;
+       int index;
+       unsigned int byte_size;
+
+       /* run buffer thread */
+       g_mutex_lock(&v4l2_handle->buffer_lock);
+
+       while (v4l2_handle->buffer_thread_run) {
+               g_mutex_unlock(&v4l2_handle->buffer_lock);
+
+               if (__vision_source_v4l2_wait_frame(v4l2_handle->device_fd, 5) !=
+                       VISION_SOURCE_ERROR_NONE) {
+                       LOGE("frame wait failed");
+                       g_mutex_lock(&v4l2_handle->buffer_lock);
+                       break;
+               }
+
+               g_mutex_lock(&v4l2_handle->buffer_lock);
+
+               if (v4l2_handle->buffer_thread_run == FALSE) {
+                       LOGW("stop buffer handler thread");
+                       break;
+               }
+               if (__vision_source_v4l2_dqbuf(v4l2_handle->device_fd,
+                                                                          v4l2_handle->buffer_type,
+                                                                          V4L2_MEMORY_MMAP, &index, &byte_size) !=
+                       VISION_SOURCE_ERROR_NONE) {
+                       LOGE("dqbuf failed");
+                       break;
+               }
+               g_mutex_unlock(&v4l2_handle->buffer_lock);
+               v4l2_handle->vision_source_buffers[index].planes[0].used_size =
+                               byte_size;
+
+               if (v4l2_handle->stream_callback) {
+                       v4l2_handle->stream_callback(
+                                       &v4l2_handle->vision_source_buffers[index],
+                                       v4l2_handle->stream_callback_data);
+               }
+               if (__vision_source_v4l2_qbuf(
+                                       v4l2_handle->device_fd, v4l2_handle->buffer_type,
+                                       V4L2_MEMORY_MMAP, index) != VISION_SOURCE_ERROR_NONE) {
+                       LOGE("qbuf failed");
+                       g_mutex_lock(&v4l2_handle->buffer_lock);
+                       break;
+               }
+               sched_yield();
+
+               g_mutex_lock(&v4l2_handle->buffer_lock);
+       }
+
+       g_mutex_unlock(&v4l2_handle->buffer_lock);
+
+       return NULL;
+}
+
+int source_v4l2_start_stream(vision_source_h handle, stream_cb callback,
+                                                        void *user_data)
+{
+       VISION_SOURCE_NULL_ARG_CHECK(handle);
+
+       vision_source_v4l2_s *v4l2_handle = (vision_source_v4l2_s *) handle;
+
+       g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&v4l2_handle->lock);
+
+       int ret = __vision_source_start_stream(
+                       v4l2_handle, v4l2_handle->stream_format.pixel_format,
+                       &v4l2_handle->stream_format.resolution,
+                       v4l2_handle->stream_format.fps, BUFFER_MAX);
+       if (ret != VISION_SOURCE_ERROR_NONE) {
+               LOGE("__vision_source_start_stream failed[0x%x]", ret);
+               g_mutex_unlock(&v4l2_handle->lock);
+               return ret;
+       }
+
+       g_mutex_lock(&v4l2_handle->buffer_lock);
+
+       v4l2_handle->buffer_thread_run = TRUE;
+       v4l2_handle->stream_callback = callback;
+       v4l2_handle->stream_callback_data = user_data;
+
+       v4l2_handle->buffer_thread = g_thread_try_new("vision_source_buffer_thread",
+                                                                                                 __fetch_buffer_and_callback,
+                                                                                                 (gpointer) v4l2_handle, NULL);
+       if (!v4l2_handle->buffer_thread) {
+               LOGE("failed to create buffer handler thread");
+               g_mutex_unlock(&v4l2_handle->buffer_lock);
+               __vision_source_stop_stream(v4l2_handle, v4l2_handle->buffer_count);
+
+               return ret;
+       }
+
+       g_mutex_unlock(&v4l2_handle->buffer_lock);
+
+       LOGD("start preview done");
+
+       return VISION_SOURCE_ERROR_NONE;
+}
+
+int source_v4l2_stop_stream(vision_source_h handle)
+{
+       VISION_SOURCE_NULL_ARG_CHECK(handle);
+
+       vision_source_v4l2_s *v4l2_handle = (vision_source_v4l2_s *) handle;
+
+       LOGD("start");
+       g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&v4l2_handle->lock);
+
+       g_mutex_lock(&v4l2_handle->buffer_lock);
+
+       v4l2_handle->buffer_thread_run = FALSE;
+
+       g_mutex_unlock(&v4l2_handle->buffer_lock);
+
+       int ret =
+                       __vision_source_stop_stream(v4l2_handle, v4l2_handle->buffer_count);
+
+       /* wait for preview thread exit */
+       if (v4l2_handle->buffer_thread)
+               g_thread_join(v4l2_handle->buffer_thread);
+       v4l2_handle->buffer_thread = NULL;
+
+       LOGD("stop preview done [0x%x]", ret);
+
+       return VISION_SOURCE_ERROR_NONE;
+}
+
 void attach_backend(vision_source_func_s *funcp)
 {
        funcp->init = vision_source_v4l2_init;
@@ -787,4 +1111,6 @@ void attach_backend(vision_source_func_s *funcp)
        funcp->open_device = vision_source_v4l2_open_device;
        funcp->close_device = vision_source_v4l2_close_device;
        funcp->set_stream_format = vision_source_v4l2_set_stream_format;
+       funcp->start_stream = source_v4l2_start_stream;
+       funcp->stop_stream = source_v4l2_stop_stream;
 }
\ No newline at end of file
index a940be6dc1c5bc644a6e3c3df8d272e1f8e6f62a..d28bbc1e4025e42d1c0c1c80bd3de37510f45b84 100644 (file)
@@ -78,6 +78,8 @@ typedef struct _vision_source_v4l2_s
 
        /* stream */
        struct vision_source_format stream_format;
+       stream_cb stream_callback;
+       gpointer stream_callback_data;
 
        /* etc */
        GMutex lock;
index 5e6e26a5bafdb3946d699c80c0eddc27016c1d75..72e0698c57755a292e280da23574604b9be92a7e 100644 (file)
@@ -69,4 +69,97 @@ TEST_F(VisionV4L2, SetStream)
        EXPECT_EQ(vision_source_set_stream_format(ms_handle, &format),
                          VISION_SOURCE_ERROR_NONE);
        EXPECT_EQ(vision_source_close_device(ms_handle), VISION_SOURCE_ERROR_NONE);
+}
+
+class VisionV4L2FixedFormat : public ::testing::Test
+{
+protected:
+       void SetUp() override
+       {
+               ASSERT_EQ(vision_source_init(&ms_handle), VISION_SOURCE_ERROR_NONE);
+               vision_source_device_info_list_s dev_list;
+               ASSERT_EQ(vision_source_enumerate_devices(ms_handle, &dev_list),
+                                 VISION_SOURCE_ERROR_NONE);
+               ASSERT_GT(dev_list.count, 0);
+
+               ASSERT_GT(dev_list.device_info[0].pixel_list.count, 0);
+               vision_source_format_s format;
+               format.pixel_format =
+                               dev_list.device_info[0].pixel_list.pixels[0].pixel_format;
+               ASSERT_GT(dev_list.device_info[0]
+                                                 .pixel_list.pixels[0]
+                                                 .resolution_list.count,
+                                 0);
+
+               format.resolution.width = dev_list.device_info[0]
+                                                                                 .pixel_list.pixels[0]
+                                                                                 .resolution_list.resolutions[0]
+                                                                                 .resolution.width;
+               format.resolution.height = dev_list.device_info[0]
+                                                                                  .pixel_list.pixels[0]
+                                                                                  .resolution_list.resolutions[0]
+                                                                                  .resolution.height;
+               ASSERT_GT(dev_list.device_info[0]
+                                                 .pixel_list.pixels[0]
+                                                 .resolution_list.resolutions[0]
+                                                 .fps_list.count,
+                                 0);
+               format.fps = dev_list.device_info[0]
+                                                        .pixel_list.pixels[0]
+                                                        .resolution_list.resolutions[0]
+                                                        .fps_list.fps[0];
+
+               ASSERT_EQ(vision_source_open_device(ms_handle, 0),
+                                 VISION_SOURCE_ERROR_NONE);
+               EXPECT_EQ(vision_source_set_stream_format(ms_handle, &format),
+                                 VISION_SOURCE_ERROR_NONE);
+       }
+
+       void TearDown() override
+       {
+               EXPECT_EQ(vision_source_close_device(ms_handle),
+                                 VISION_SOURCE_ERROR_NONE);
+               ASSERT_EQ(vision_source_exit(ms_handle), VISION_SOURCE_ERROR_NONE);
+       }
+       vision_source_h ms_handle;
+};
+
+TEST_F(VisionV4L2FixedFormat, OnlyStopStream)
+{
+       EXPECT_EQ(vision_source_stop_stream(ms_handle), VISION_SOURCE_ERROR_NONE);
+}
+
+TEST_F(VisionV4L2FixedFormat, StartStream)
+{
+       ASSERT_EQ(vision_source_start_stream(ms_handle, nullptr, nullptr),
+                         VISION_SOURCE_ERROR_NONE);
+       sleep(1);
+       EXPECT_EQ(vision_source_stop_stream(ms_handle), VISION_SOURCE_ERROR_NONE);
+}
+
+static int test_cb(vision_source_buffer_s *buffer, void *user_data)
+{
+       FILE *file;
+       clock_t end = clock();
+       clock_t *start = (clock_t *) user_data;
+       char filename[128];
+
+       double delta_ms = (double) (end - *start) / CLOCKS_PER_SEC * 1000;
+
+       snprintf(filename, 127, "out_%04u.data", (unsigned) delta_ms);
+       file = fopen(filename, "w");
+
+       fwrite(buffer->planes[0].data, sizeof(unsigned char),
+                  buffer->planes[0].used_size, file);
+       fclose(file);
+       return 0;
+}
+
+TEST_F(VisionV4L2FixedFormat, StartWithCallback)
+{
+       clock_t start = clock();
+       ASSERT_EQ(vision_source_start_stream(ms_handle, test_cb, &start),
+                         VISION_SOURCE_ERROR_NONE);
+       sleep(1);
+       EXPECT_EQ(vision_source_stop_stream(ms_handle), VISION_SOURCE_ERROR_NONE);
 }
\ No newline at end of file