diff --git a/Makefile b/Makefile index 60f7c6e..e6b74ba 100644 --- a/Makefile +++ b/Makefile @@ -8,20 +8,20 @@ EXE=gwc # https://github.com/theicfire/makefiletutorial # # -BLIBS := $(shell pkg-config --libs --cflags gstreamer-webrtc-1.0 gstreamer-sdp-1.0 libsoup-3.0 json-glib-1.0) +BLIBS := $(shell pkg-config --libs --cflags gstreamer-webrtc-1.0 gstreamer-sdp-1.0 libsoup-3.0 json-glib-1.0 libudev) -CFLAGS := $(CFLAGS) $$(pkg-config --cflags glib-2.0 gstreamer-1.0 json-glib-1.0 gstreamer-webrtc-1.0 gstreamer-sdp-1.0 libsoup-3.0 sqlite3) -LIBS=$$(pkg-config --libs glib-2.0 gstreamer-1.0 gstreamer-webrtc-1.0 gstreamer-sdp-1.0 gstreamer-app-1.0 gstreamer-base-1.0 libsoup-3.0 json-glib-1.0 sqlite3) +CFLAGS := $(CFLAGS) $$(pkg-config --cflags glib-2.0 gstreamer-1.0 json-glib-1.0 gstreamer-webrtc-1.0 gstreamer-sdp-1.0 libsoup-3.0 sqlite3 libudev) +LIBS=$$(pkg-config --libs glib-2.0 gstreamer-1.0 gstreamer-webrtc-1.0 gstreamer-sdp-1.0 gstreamer-app-1.0 gstreamer-base-1.0 libsoup-3.0 json-glib-1.0 sqlite3 libudev) all: webrtc-sendonly rtspsrc-webrtc gwc -webrtc-sendonly: webrtc-sendonly.c v4l2ctl.c common_priv.c +webrtc-sendonly: webrtc-sendonly.c v4l2ctl.c common_priv.c media.c "$(CC)" $(CFLAGS) $^ $(BLIBS) -o $@ -rtspsrc-webrtc: rtspsrc-webrtc.c v4l2ctl.c common_priv.c +rtspsrc-webrtc: rtspsrc-webrtc.c v4l2ctl.c common_priv.c media.c "$(CC)" $(CFLAGS) $^ $(BLIBS) -o $@ -gwc: v4l2ctl.c sql.c soup.c gst-app.c main.c common_priv.c +gwc: v4l2ctl.c sql.c soup.c gst-app.c main.c common_priv.c media.c "${CC}" -Wall -g -O0 ${CFLAGS} $^ ${LIBS} -o $@ diff --git a/config.example b/config.example index bd3c987..7fee31b 100644 --- a/config.example +++ b/config.example @@ -1,6 +1,6 @@ { "http": { - "port": 57778, + "port": 9001, "host": "127.0.0.1", "user": "test", "password": "testsoup" @@ -10,6 +10,7 @@ "height": 600, "framerate": 30, "io_mode": 2, + "devtype": "USB", /* USB for uvc camera, I2C for DVP and CSI camera */ "device": "/dev/video0", "type": "image/jpeg", "format": "NV12" diff --git a/config.jetson b/config.jetson index 9a2d7ca..21af597 100644 --- a/config.jetson +++ b/config.jetson @@ -1,7 +1,7 @@ { "videnc": "h264", "http": { - "port": 57778, + "port": 9001, "host": "127.0.0.1", "user": "test", "password": "testsoup" @@ -11,6 +11,7 @@ "height": 2464, "framerate": 21, "io_mode": 2, + "devtype": "USB", /* USB for uvc camera, I2C for DVP and CSI camera */ "device": "/dev/video0", "type": "video/x-raw(memory:NVMM)", "format": "NV12" diff --git a/data_struct.h b/data_struct.h index b83e086..8579aaa 100644 --- a/data_struct.h +++ b/data_struct.h @@ -54,6 +54,8 @@ struct _http_data { typedef struct { gchar *device; + gchar *devtype; + gchar *spec_drv; // specified command line, i.e: gst-launch-1.0 -v v4l2src device=${device} num-buffers=-1 ... int32_t width; int32_t height; int32_t framerate; diff --git a/gst-app.c b/gst-app.c index 8943643..33d4c32 100644 --- a/gst-app.c +++ b/gst-app.c @@ -58,6 +58,7 @@ static GMutex G_appsrc_lock; static GList *G_AppsrcList; GstConfigData config_data; +GHashTable *capture_htable = NULL; #define MAKE_ELEMENT_AND_ADD(elem, name) \ G_STMT_START { \ @@ -667,7 +668,7 @@ static GstElement *get_audio_src() { filter = gst_element_factory_make("ladspa-sine-so-sine-faaa", NULL); if (!teesrc || !source || !amp || !postconv || !enc || !filter) { - g_printerr("audio source all elements could be created.\n"); + g_warning("audio source all elements could be created.\n"); return NULL; } @@ -718,7 +719,7 @@ static GstPadLinkReturn link_request_src_pad_with_dst_name(GstElement *src, GstE if ((lret = gst_pad_link(src_pad, sink_pad)) != GST_PAD_LINK_OK) { gchar *sname = gst_pad_get_name(src_pad); gchar *dname = gst_pad_get_name(sink_pad); - g_print("Src pad %s link to sink pad %s failed . return: %s\n", sname, dname, get_link_error(lret)); + g_print("1Src pad %s link to sink pad %s failed . return: %s\n", sname, dname, get_link_error(lret)); get_pad_caps_info(src_pad); get_pad_caps_info(sink_pad); g_free(sname); @@ -741,6 +742,7 @@ static GstPadLinkReturn link_request_src_pad(GstElement *src, GstElement *dst) { gst_element_class_get_metadata(klass, GST_ELEMENT_METADATA_KLASS); // g_print("class name:%s\n", klassname); #if GST_VERSION_MINOR >= 20 + g_print("GST_VERSION_MINOR >=20\n"); src_pad = gst_element_request_pad_simple(src, "src_%u"); if(src_pad == NULL) @@ -756,7 +758,7 @@ static GstPadLinkReturn link_request_src_pad(GstElement *src, GstElement *dst) { if ((lret = gst_pad_link(src_pad, sink_pad)) != GST_PAD_LINK_OK) { gchar *sname = gst_pad_get_name(src_pad); gchar *dname = gst_pad_get_name(sink_pad); - g_print("Src pad %s link to sink pad %s failed . return: %s\n", sname, dname, get_link_error(lret)); + g_print("2Src pad %s link to sink pad %s failed . return: %s\n", sname, dname, get_link_error(lret)); get_pad_caps_info(src_pad); get_pad_caps_info(sink_pad); g_free(sname); @@ -2093,32 +2095,25 @@ void start_udpsrc_webrtcbin(WebrtcItem *item) { gchar *upenc = g_ascii_strup(config_data.videnc, strlen(config_data.videnc)); // here must have rtph264depay and rtph264pay to be compatible with mobile browser. - if (g_str_has_prefix(config_data.videnc, "h26")) - { -#if defined(HAS_JETSON_NANO) - video_src = g_strdup_printf("udpsrc port=%d multicast-group=%s socket-timestamp=1 ! " - " application/x-rtp,media=(string)video,clock-rate=(int)90000,encoding-name=(string)%s,payload=(int)96 ! " - " rtp%sdepay ! rtp%spay config-interval=-1 aggregate-mode=1 ! %s. ", - config_data.webrtc.udpsink.port, config_data.webrtc.udpsink.addr, upenc, config_data.videnc, config_data.videnc, webrtc_name); -#else + if (g_str_has_prefix(config_data.videnc, "h26")) { gchar *rtp = get_rtp_args(); video_src = g_strdup_printf("udpsrc port=%d multicast-group=%s multicast-iface=lo socket-timestamp=1 ! " " application/x-rtp,media=(string)video,clock-rate=(int)90000,encoding-name=(string)%s,payload=(int)96 ! " " %s ! rtp%spay config-interval=-1 aggregate-mode=1 ! %s. ", config_data.webrtc.udpsink.port, config_data.webrtc.udpsink.addr, upenc, rtp, config_data.videnc, webrtc_name); + g_free(rtp); -#endif } else video_src = g_strdup_printf("udpsrc port=%d multicast-group=%s multicast-iface=lo socket-timestamp=1 ! " " application/x-rtp,media=(string)video,clock-rate=(int)90000,encoding-name=(string)%s,payload=(int)96 ! " " %s. ", config_data.webrtc.udpsink.port, config_data.webrtc.udpsink.addr, upenc, webrtc_name); - g_free(upenc); + g_free(upenc); if (audio_source != NULL) { gchar *audio_src = udpsrc_audio_cmdline(webrtc_name); cmdline = g_strdup_printf("webrtcbin name=%s stun-server=stun://%s %s %s ", webrtc_name, config_data.webrtc.stun, audio_src, video_src); - g_print("webrtc cmdline: %s \n", cmdline); + // g_print("webrtc cmdline: %s \n", cmdline); g_free(audio_src); g_free(video_src); } else { @@ -2128,7 +2123,7 @@ void start_udpsrc_webrtcbin(WebrtcItem *item) { cmdline = g_strdup_printf("webrtcbin name=%s stun-server=stun://%s %s", webrtc_name, config_data.webrtc.stun, video_src); // g_free(turn_srv); } - + // g_print("webrtc cmdline: %s \n", cmdline); item->sendpipe = gst_parse_launch(cmdline, NULL); gst_element_set_state(item->sendpipe, GST_STATE_READY); @@ -3073,6 +3068,7 @@ int edgedect_hlssink() { static void _initial_device() { if (is_initial) return; + _mkdir(config_data.root_dir, 0755); record_time = config_data.rec_len; @@ -3125,10 +3121,33 @@ GThread *start_inotify_thread(void) { GstElement *create_instance() { pipeline = gst_pipeline_new("pipeline"); + + if (!capture_htable) + capture_htable = initial_capture_hashtable(); + + if (config_data.v4l2src_data.spec_drv) { + _v4l2src_data *data = &config_data.v4l2src_data; + + g_print("found specfic capture driver is: %s\n", data->spec_drv); + gchar *cmdformat = g_hash_table_lookup(capture_htable, data->spec_drv); + gchar *cmdline = g_strdup_printf(cmdformat, data->device, data->format, data->width, + data->height, data->framerate, + config_data.webrtc.udpsink.addr, + config_data.webrtc.udpsink.port); + GstElement *cmdlinebin = gst_parse_launch(cmdline, NULL); + gst_element_set_state(cmdlinebin, GST_STATE_READY); + g_print("run cmdline: %s\n", cmdline); + gst_element_set_state(cmdlinebin, GST_STATE_PLAYING); + g_free(cmdline); + + // gst_element_sync_state_with_parent(cmdlinebin); + gst_bin_add(GST_BIN(pipeline), cmdlinebin); + return pipeline; + } + if (!is_initial) _initial_device(); - // start_av_fakesink(); if (config_data.splitfile_sink.enable) splitfile_sink(); @@ -3156,8 +3175,8 @@ GstElement *create_instance() { start_av_appsink(); } - if (config_data.webrtc.enable) { + if (config_data.webrtc.enable) start_av_udpsink(); - } + return pipeline; } diff --git a/main.c b/main.c index 3492d3c..4e93bec 100644 --- a/main.c +++ b/main.c @@ -164,6 +164,7 @@ static void read_config_json(gchar *fullpath) { object = json_object_get_object_member(root_obj, "v4l2src"); config_data.v4l2src_data.device = g_strdup(json_object_get_string_member(object, "device")); + config_data.v4l2src_data.devtype = g_strdup(json_object_get_string_member(object, "devtype")); config_data.v4l2src_data.format = g_strdup(json_object_get_string_member(object, "format")); config_data.v4l2src_data.type = g_strdup(json_object_get_string_member(object, "type")); @@ -390,8 +391,8 @@ int main(int argc, char *argv[]) { exit(1); } - if (!find_video_device_fmt(&config_data.v4l2src_data,TRUE) && - !get_capture_device(&config_data.v4l2src_data)) { + if (!find_video_device_fmt(&config_data.v4l2src_data, TRUE) + && !get_capture_device(&config_data.v4l2src_data)) { g_error("No video capture device found!!!\n"); exit(1); } diff --git a/media.c b/media.c new file mode 100644 index 0000000..534ac18 --- /dev/null +++ b/media.c @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2019-2020 Paul Kocialkowski + * Copyright (C) 2020 Bootlin + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +int media_device_info(int media_fd, struct media_device_info *device_info) +{ + int ret; + + ret = ioctl(media_fd, MEDIA_IOC_DEVICE_INFO, device_info); + if (ret) + return -errno; + + return 0; +} + +int media_topology_get(int media_fd, struct media_v2_topology *topology) +{ + int ret; + + ret = ioctl(media_fd, MEDIA_IOC_G_TOPOLOGY, topology); + if (ret) + return -errno; + + return 0; +} + +struct media_v2_entity *media_topology_entity_find_by_name(struct media_v2_topology *topology, + const char *name) { + struct media_v2_entity *entities; + unsigned int i; + + if (!topology || !topology->num_entities || !topology->ptr_entities) + return NULL; + + entities = (struct media_v2_entity *)topology->ptr_entities; + + for (i = 0; i < topology->num_entities; i++) { + struct media_v2_entity *entity = &entities[i]; + printf("entity name: %s, %s\n", entity->name, name); + if (strncmp(name, entity->name, strlen(name)) == 0) + return entity; + } + + return NULL; +} + +struct media_v2_entity *media_topology_entity_find_by_function(struct media_v2_topology *topology, + unsigned int function) +{ + struct media_v2_entity *entities; + unsigned int i; + + if (!topology || !topology->num_entities || !topology->ptr_entities) + return NULL; + + entities = (struct media_v2_entity *)topology->ptr_entities; + + for (i = 0; i < topology->num_entities; i++) { + struct media_v2_entity *entity = &entities[i]; + + if (entity->function == function) + return entity; + } + + return NULL; +} + +struct media_v2_interface *media_topology_interface_find_by_id(struct media_v2_topology *topology, + unsigned int id) +{ + struct media_v2_interface *interfaces; + unsigned int interfaces_count; + unsigned int i; + + if (!topology || !topology->num_interfaces || !topology->ptr_interfaces) + return NULL; + + interfaces = (struct media_v2_interface *)topology->ptr_interfaces; + interfaces_count = topology->num_interfaces; + + for (i = 0; i < interfaces_count; i++) { + struct media_v2_interface *interface = &interfaces[i]; + + if (interface->id == id) + return interface; + } + + return NULL; +} + +struct media_v2_pad *media_topology_pad_find_by_entity(struct media_v2_topology *topology, + unsigned int entity_id, + unsigned int flags) +{ + struct media_v2_pad *pads; + unsigned int pads_count; + unsigned int i; + + if (!topology || !topology->num_pads || !topology->ptr_pads) + return NULL; + + pads = (struct media_v2_pad *)topology->ptr_pads; + pads_count = topology->num_pads; + + for (i = 0; i < pads_count; i++) { + struct media_v2_pad *pad = &pads[i]; + + if (pad->entity_id == entity_id && + (pad->flags & flags) == flags) + return pad; + } + + return NULL; +} + +struct media_v2_pad *media_topology_pad_find_by_id(struct media_v2_topology *topology, + unsigned int id) +{ + struct media_v2_pad *pads; + unsigned int pads_count; + unsigned int i; + + if (!topology || !topology->num_pads || !topology->ptr_pads) + return NULL; + + pads = (struct media_v2_pad *)topology->ptr_pads; + pads_count = topology->num_pads; + + for (i = 0; i < pads_count; i++) { + struct media_v2_pad *pad = &pads[i]; + + if (pad->id == id) + return pad; + } + + return NULL; +} + +struct media_v2_link *media_topology_link_find_by_pad(struct media_v2_topology *topology, + unsigned int pad_id, + unsigned int pad_flags) +{ + struct media_v2_link *links; + unsigned int links_count; + unsigned int i; + + if (!topology || !topology->num_links || !topology->ptr_links) + return NULL; + + links = (struct media_v2_link *)topology->ptr_links; + links_count = topology->num_links; + + for (i = 0; i < links_count; i++) { + struct media_v2_link *link = &links[i]; + + if ((pad_flags & MEDIA_PAD_FL_SINK && link->sink_id == pad_id) || + (pad_flags & MEDIA_PAD_FL_SOURCE && link->source_id == pad_id)) + return link; + } + + return NULL; +} + +struct media_v2_link *media_topology_link_find_by_entity(struct media_v2_topology *topology, + unsigned int entity_id, + unsigned int pad_flags) +{ + struct media_v2_link *links; + unsigned int links_count; + unsigned int i; + + if (!topology || !topology->num_links || !topology->ptr_links) + return NULL; + + links = (struct media_v2_link *)topology->ptr_links; + links_count = topology->num_links; + + for (i = 0; i < links_count; i++) { + struct media_v2_link *link = &links[i]; + + if ((pad_flags & MEDIA_PAD_FL_SINK && link->sink_id == entity_id) || + (pad_flags & MEDIA_PAD_FL_SOURCE && link->source_id == entity_id)) + return link; + } + + return NULL; +} + +int media_request_alloc(int media_fd) +{ + int request_fd; + int ret; + + ret = ioctl(media_fd, MEDIA_IOC_REQUEST_ALLOC, &request_fd); + if (ret) + return -errno; + + return request_fd; +} + +int media_request_queue(int request_fd) +{ + int ret; + + ret = ioctl(request_fd, MEDIA_REQUEST_IOC_QUEUE, NULL); + if (ret) + return -errno; + + return 0; +} + +int media_request_reinit(int request_fd) +{ + int ret; + + ret = ioctl(request_fd, MEDIA_REQUEST_IOC_REINIT, NULL); + if (ret) + return -errno; + + return 0; +} + +int media_request_poll(int request_fd, struct timeval *timeout) +{ + fd_set except_fds; + int ret; + + FD_ZERO(&except_fds); + FD_SET(request_fd, &except_fds); + + ret = select(request_fd + 1, NULL, NULL, &except_fds, timeout); + if (ret < 0) + return -errno; + + if (!FD_ISSET(request_fd, &except_fds)) + return 0; + + return ret; +} diff --git a/media.h b/media.h new file mode 100644 index 0000000..7a08856 --- /dev/null +++ b/media.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2019-2020 Paul Kocialkowski + * Copyright (C) 2020 Bootlin + */ + +#ifndef _MEDIA_H_ +#define _MEDIA_H_ + +#include +#include + +int media_device_info(int media_fd, struct media_device_info *device_info); +int media_topology_get(int media_fd, struct media_v2_topology *topology); +struct media_v2_entity *media_topology_entity_find_by_function(struct media_v2_topology *topology, + unsigned int function); +struct media_v2_entity *media_topology_entity_find_by_name(struct media_v2_topology *topology, + const char *name); +struct media_v2_interface *media_topology_interface_find_by_id(struct media_v2_topology *topology, + unsigned int id); +struct media_v2_pad *media_topology_pad_find_by_entity(struct media_v2_topology *topology, + unsigned int entity_id, + unsigned int flags); +struct media_v2_pad *media_topology_pad_find_by_id(struct media_v2_topology *topology, + unsigned int id); +struct media_v2_link *media_topology_link_find_by_pad(struct media_v2_topology *topology, + unsigned int pad_id, + unsigned int pad_flags); +struct media_v2_link *media_topology_link_find_by_entity(struct media_v2_topology *topology, + unsigned int entity_id, + unsigned int pad_flags); +int media_request_alloc(int media_fd); +int media_request_queue(int request_fd); +int media_request_reinit(int request_fd); +int media_request_poll(int request_fd, struct timeval *timeout); + +#endif diff --git a/soup.c b/soup.c index f749b22..156e56a 100644 --- a/soup.c +++ b/soup.c @@ -667,7 +667,7 @@ static gchar *get_auth_value_by_key(const gchar *auth, const gchar *key) { } // g_print("name: %s, value: %s\n", name, value); } - g_free(pairs); + g_strfreev(pairs); return realm; } diff --git a/v4l2ctl.c b/v4l2ctl.c index c6c53dd..78589d6 100644 --- a/v4l2ctl.c +++ b/v4l2ctl.c @@ -20,12 +20,43 @@ * Boston, MA 02110-1301, USA. */ #include "v4l2ctl.h" +#include "media.h" +#include #include +#include #include -#include +#include static int ctrl_list[] = {V4L2_CID_BRIGHTNESS, V4L2_CID_CONTRAST, V4L2_CID_AUTO_WHITE_BALANCE, V4L2_CID_SHARPNESS, V4L2_CID_WHITENESS}; +const char *video_driver[] = { + "sun6i-csi", + "imx-media"}; + +const char *video_capture[] = { + "sun6i-csi-capture", + "ipu1_csi1 capture"}; + +GHashTable *initial_capture_hashtable() { + GHashTable *hash = g_hash_table_new(g_str_hash, g_str_equal); + + g_hash_table_insert(hash, "sun6i-csi-capture", + "v4l2src device=%s num-buffers=-1 !" + " video/x-raw,pixelformat=%s,width=%d, height=%d,framerate=%d/1 !" + " v4l2h264enc capture-io-mode=4 output-io-mode=4 ! queue !" + " rtph264pay config-interval=1 pt=96 ! " + " udpsink host=%s port=%d auto-multicast=true async=false sync=false"); + g_hash_table_insert(hash, "ipu1_csi1 capture", "Treats"); + + printf("There are %d keys in the hash table\n", + g_hash_table_size(hash)); + + // printf("Jazzy likes %s\n", g_hash_table_lookup(hash, "Jazzy")); + + // g_hash_table_destroy(hash); + return hash; +} + static gchar * get_string_from_json_object(JsonObject *object) { JsonNode *root; @@ -54,7 +85,7 @@ get_device_json(const gchar *device) { JsonObject *ctrlJson; // Open the device file - fd = open(device, O_RDWR); + fd = open(device, O_RDWR | O_NONBLOCK); if (fd < 0) { return NULL; } @@ -336,6 +367,16 @@ void print_frmival(struct v4l2_frmivalenum *frmival, const char *prefix) { } } +static int device_cap_info(int fd, struct v4l2_capability *caps) { + int ret; + + ret = ioctl(fd, VIDIOC_QUERYCAP, caps); + if (ret) + return -errno; + + return 0; +} + int dump_video_device_fmt(const gchar *device) { int fd; gchar *tmp = NULL; @@ -384,6 +425,105 @@ int dump_video_device_fmt(const gchar *device) { return 0; } +static gchar *get_video_path(struct udev *udev, int major, int minor) { + gchar *path = NULL; + struct udev_device *device; + dev_t devnum; + devnum = makedev(major, minor); + device = udev_device_new_from_devnum(udev, 'c', devnum); + if (!device) { + return NULL; + } + // will get path as /dev/videoX + path = g_strdup(udev_device_get_devnode(device)); + g_print("get devnode path is: %s\n", path); + udev_device_unref(device); + return path; +} + +#if 0 +static gchar *get_udev_path(int major, int minor) { + gchar *tmp= g_strdup_printf("%s/%d:%d/uevent", "/sys/dev/char", major, minor); + return tmp; +} + +static gchar *get_udev_devnode_name(const gchar *uevent_path) { + FILE *fp; + gchar *video_path = NULL; + gchar *line = NULL; + size_t len = 0; + ssize_t read; + /** + * @brief + * example of read from sysfs. + * ~$ cat /sys/dev/char/81\:6/uevent + * MAJOR = 81 + * MINOR = 6 + * DEVNAME = video4 + */ + fp = fopen(uevent_path, "r"); + if (fp == NULL) + return NULL; + + while ((read = getline(&line, &len, fp)) != -1) { + // g_print("Retrieved line of length %zu :\n", read); + // g_print("%s", line); + char **pairs; + pairs = g_strsplit(line, "=", 2); + if (pairs[0] != NULL && pairs[1] != NULL) { + g_strchomp(pairs[0]); + if (g_strcmp0(pairs[0], "DEVNAME") == 0 && g_str_has_prefix(pairs[1], "video")) { + g_strchomp(pairs[1]); + video_path = g_strdup_printf("/dev/%s", pairs[1]); + struct v4l2_capability caps; + // Open the device file + int fd = open(video_path, O_RDWR | O_NONBLOCK); + if (fd > 0) { + if (0 == device_cap_info(fd, &caps)) { + // g_print("csi ioctl: cap info: %s\n", (const gchar *)(caps.bus_info)); + if (caps.device_caps & V4L2_CAP_VIDEO_CAPTURE) { + break; + } + } + close(fd); + } + } + } + + g_strfreev(pairs); + } + fclose(fp); + + return video_path; +} +#endif + +static void get_capture_fmt_video(_v4l2src_data *data) { + struct v4l2_format vfmt; + struct v4l2_capability caps; + int fd = open(data->device, O_RDWR | O_NONBLOCK); + if (fd > 0) { + if (0 == device_cap_info(fd, &caps)) { + if (caps.device_caps & V4L2_CAP_VIDEO_CAPTURE) { + memset(&vfmt, 0, sizeof(vfmt)); + vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vfmt.fmt.pix.width = 640; + vfmt.fmt.pix.height = 480; + vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_NV12; + vfmt.fmt.pix.field = V4L2_FIELD_INTERLACED; + // vfmt.fmt.pix.priv = priv_magic; + // vfmt.type = vidout_buftype; + if (ioctl(fd, VIDIOC_S_FMT, &vfmt) == 0){ + data->width = vfmt.fmt.pix.width; + data->height = vfmt.fmt.pix.height; + data->format = fcc2s(vfmt.fmt.pix.pixelformat); + } + } + } + close(fd); + } +} + static gboolean get_default_capture_device(_v4l2src_data *data) { // This is used if the wrong video configuration is set but a valid device is found on the system. gboolean match = FALSE; @@ -400,12 +540,12 @@ static gboolean get_default_capture_device(_v4l2src_data *data) { fmtdesc.index = 0; fmtdesc.mbus_code = 0; - fd = open(data->device, O_RDWR); + fd = open(data->device, O_RDWR | O_NONBLOCK); if (fd < 0) { return match; } - if (0 > ioctl(fd, VIDIOC_QUERYCAP, &capability)) { + if (0 != device_cap_info(fd, &capability)) { goto invalid_dev; } @@ -413,7 +553,7 @@ static gboolean get_default_capture_device(_v4l2src_data *data) { goto invalid_dev; } - for (; 0 == ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) && !match ; fmtdesc.index++) { + for (; 0 == ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) && !match; fmtdesc.index++) { frmsize.pixel_format = fmtdesc.pixelformat; frmsize.index = 0; for (; 0 == ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsize) && !match; frmsize.index++) { @@ -436,19 +576,522 @@ static gboolean get_default_capture_device(_v4l2src_data *data) { } invalid_dev: + g_print("not found current size\n"); close(fd); return match; } +#if 0 +static int enumerate_entity_desc(int media_fd, struct media_v2_topology *topology) { + struct media_entity_desc ent_desc; + memset(&ent_desc, 0, sizeof(ent_desc)); + ent_desc.id = MEDIA_ENT_ID_FLAG_NEXT; + while (!ioctl(media_fd, MEDIA_IOC_ENUM_ENTITIES, &ent_desc)) { + g_print("entities name: %s, major: %d, minor: %d \n", + (const gchar *)(ent_desc.name), ent_desc.dev.major, ent_desc.dev.minor); + + gchar *ueventpath = get_udev_path(ent_desc.dev.major, ent_desc.dev.minor); + gchar *video_path = get_udev_devnode_name(ueventpath); + g_free(ueventpath); + if (video_path) { + g_print("find video name: %s, major: %d, minor: %d \n", + (const gchar *)(ent_desc.name), ent_desc.dev.major, ent_desc.dev.minor); + } + + + struct media_links_enum links_enum; + struct media_pad_desc pads[ent_desc.pads]; + struct media_link_desc links[ent_desc.links]; + + memset(&links_enum, 0, sizeof(links_enum)); + links_enum.entity = ent_desc.id; + links_enum.pads = pads; + links_enum.links = links; + if (ioctl(media_fd, MEDIA_IOC_ENUM_LINKS, &links_enum)) + return ent_desc.id; + + for (unsigned i = 0; i < ent_desc.pads; i++) + printf("\tPad : %u\n", pads[i].index); + for (unsigned i = 0; i < ent_desc.links; i++) + printf("\tLink : %u->%u:\n", + links[i].source.entity, + links[i].sink.entity); + + ent_desc.id |= MEDIA_ENT_ID_FLAG_NEXT; + } + + return 0; +} + +#endif + +static gchar *find_capture_path_by_udev(int media_fd, struct media_v2_topology *topology, + struct udev *udev, const gchar *capture) { + struct media_entity_desc ent_desc; + gchar *video_path = NULL; + memset(&ent_desc, 0, sizeof(ent_desc)); + ent_desc.id = MEDIA_ENT_ID_FLAG_NEXT; + while (!ioctl(media_fd, MEDIA_IOC_ENUM_ENTITIES, &ent_desc)) { + // g_print("entities name: %s, major: %d, minor: %d \n", + // (const gchar *)(ent_desc.name), ent_desc.dev.major, ent_desc.dev.minor); + if(g_strcmp0(ent_desc.name,capture) == 0) { + video_path = get_video_path(udev, ent_desc.v4l.major, ent_desc.v4l.minor); + if (video_path) { + // g_print("find video name: %s, major: %d, minor: %d , path: %s \n", + // (const gchar *)(ent_desc.name), ent_desc.dev.major, ent_desc.dev.minor, video_path); + + break; + } + } + + ent_desc.id |= MEDIA_ENT_ID_FLAG_NEXT; + } + return video_path; +} + +static int media_device_probe(_v4l2src_data *data, struct udev *udev, + const char *path) { + + struct media_device_info device_info = {0}; + struct media_v2_topology topology = {0}; + // struct media_v2_entity *encoder_entity; + // struct media_v2_interface *encoder_interface; + + + int media_fd = -1; + + int ret = -1, i; + + media_fd = open(path, O_RDWR | O_NONBLOCK); + if (media_fd < 0) + return -errno; + + ret = media_device_info(media_fd, &device_info); + if (ret) + return -errno; + + ret = media_topology_get(media_fd, &topology); + + struct media_v2_entity v2_ents[topology.num_entities]; + struct media_v2_interface v2_ifaces[topology.num_interfaces]; + struct media_v2_pad v2_pads[topology.num_pads]; + struct media_v2_link v2_links[topology.num_links]; + + topology.ptr_entities = (__u64)v2_ents; + topology.ptr_interfaces = (__u64)v2_ifaces; + topology.ptr_pads = (__u64)v2_pads; + topology.ptr_links = (__u64)v2_links; + + ret = media_device_info(media_fd, &device_info); + if (ret) + goto perror; + + g_print("driver name: %s,bus: %s , model: %s, serial: %s\n", + (const gchar *)(device_info.driver), device_info.bus_info, device_info.model, device_info.serial); + + ret = media_topology_get(media_fd, &topology); + if (ret) + goto perror; + + if (!topology.num_interfaces || !topology.num_entities || + !topology.num_pads || !topology.num_links) { + ret = -ENODEV; + goto perror; + } + ret = -1; + // g_print("----------------------------->\n"); + for (i = 0; i < sizeof(video_capture) / sizeof(char *); i++) { + gchar *vpath = find_capture_path_by_udev(media_fd, &topology,udev, video_capture[i]); + if(vpath != NULL) { + g_print("found capture device is: %s\n",vpath); + g_free(data->device); + data->device = g_strdup(vpath); + data->spec_drv = g_strdup(video_capture[i]); + g_free(vpath); + get_capture_fmt_video(data); + ret = 0; + break; + } + } + + // g_print("<----------------------------\n"); + // encoder_entity = media_topology_entity_find_by_function(&topology, + // MEDIA_ENT_F_IO_V4L); + // if (encoder_entity) { + // g_print("encoder_entity name: %s, id: %d \n", + // (const gchar *)(encoder_entity->name), encoder_entity->id); + // } + +#if 0 + for (i = 0; i < sizeof(video_capture) / sizeof(char *); i++) { + + encoder_entity = media_topology_entity_find_by_name(&topology, video_capture[i]); + if (encoder_entity) { + g_print("supported video capture: %s\n", video_capture[i]); + for (j = 0; j < topology.num_interfaces; j++) + { + const struct media_v2_interface *iface = &v2_ifaces[j]; + if(iface->flags == encoder_entity->flags) { + + gchar *vpath = get_video_path(udev, iface->devnode.major, iface->devnode.minor); + if(vpath != NULL) { + g_print("found video capture path: %s\n", vpath); + g_free(vpath); + break; + } + } + } + break; + } + + if (0 == g_strcmp0(video_driver[i], device_info.driver)) { + break; + } + } + + if (i == sizeof(video_driver) / sizeof(char *)) { + g_print("Not found supported video driver\n"); + ret == -1; + goto perror; + } +#endif + +#if 0 + for (i = 0; i < sizeof(video_driver) / sizeof(char *); i++) { + g_print("supported video driver: %s\n", video_driver[i]); + if (0 == g_strcmp0(video_driver[i], device_info.driver)) + { + break; + } + } + + if (i == sizeof(video_driver) / sizeof(char *)) { + g_print("Not found supported video driver\n"); + ret == -1; + } +#endif + +#if 0 + struct udev_device *device; + struct media_v2_pad *sink_pad; + struct media_v2_link *sink_link; + struct media_v2_pad *source_pad; + struct media_v2_link *source_link; + dev_t devnum; + sink_pad = media_topology_pad_find_by_entity(&topology, + encoder_entity->id, + MEDIA_PAD_FL_SINK); + if (!sink_pad) { + ret = -ENODEV; + goto perror; + } + + sink_link = media_topology_link_find_by_pad(&topology, sink_pad->id, + sink_pad->flags); + if (!sink_link) { + ret = -ENODEV; + goto perror; + } + + source_pad = media_topology_pad_find_by_id(&topology, + sink_link->source_id); + if (!source_pad) { + ret = -ENODEV; + goto perror; + } + + source_link = media_topology_link_find_by_entity(&topology, + source_pad->entity_id, + MEDIA_PAD_FL_SINK); + if (!source_link) { + ret = -ENODEV; + goto perror; + } + + encoder_interface = media_topology_interface_find_by_id(&topology, + source_link->source_id); + if (!encoder_interface) { + ret = -ENODEV; + goto perror; + } + + devnum = makedev(encoder_interface->devnode.major, + encoder_interface->devnode.minor); + + device = udev_device_new_from_devnum(udev, 'c', devnum); + if (!device) { + ret = -ENODEV; + goto perror; + } + // will get path as /dev/v4l-subdev0 + path = udev_device_get_devnode(device); + g_print("get devnode path is: %s\n", path); +#endif + +perror: + if (media_fd >= 0) + close(media_fd); + + return ret; +} + +/** + * v4l2-ctl -d /dev/v4l-subdev0 -D + * Driver Info: + * Driver version : 6.9.8 + * Capabilities : 0x00000000 + * Media Driver Info: + * Driver name : sun6i-csi + * Model : Allwinner A31 CSI Device + * Serial : + * Bus info : platform:1cb0000.csi + * Media version : 6.9.8 + * Hardware revision: 0x00000000 (0) + * Driver version : 6.9.8 + * Interface Info: + * ID : 0x03000008 + * Type : V4L Sub-Device + * Entity Info: + * ID : 0x00000001 (1) + * Name : sun6i-csi-bridge + * Function : Video Interface Bridge + * Pad 0x01000002 : 0: Sink + * Link 0x02000006: from remote pad 0x1000005 of entity 'ov2640 2-0030' (Camera Sensor): Data, Enabled + * Pad 0x01000003 : 1: Source, Must Connect + * Link 0x02000010: to remote pad 0x100000d of entity 'sun6i-csi-capture' (V4L2 I/O): Data, Enabled, Immutable + */ + +static int enumerate_udev_list(_v4l2src_data *data) { + struct udev *udev = NULL; + struct udev_enumerate *enumerate = NULL; + struct udev_list_entry *devices; + struct udev_list_entry *entry; + int ret; + + udev = udev_new(); + if (!udev) + goto error; + + enumerate = udev_enumerate_new(udev); + if (!enumerate) + goto error; + + udev_enumerate_add_match_subsystem(enumerate, "media"); + udev_enumerate_scan_devices(enumerate); + + devices = udev_enumerate_get_list_entry(enumerate); + + udev_list_entry_foreach(entry, devices) { + struct udev_device *device; + const char *path; + + path = udev_list_entry_get_name(entry); + if (!path) + continue; + device = udev_device_new_from_syspath(udev, path); + if (!device) + continue; + // example path is: /sys/devices/platform/soc/1c0e000.video-codec/media0 + const char *mpath = udev_device_get_devnode(device); /* /dev/media0 */ + ret = media_device_probe(data, udev, mpath); + g_print("read path is:%s , ret: %d\n", mpath, ret); + udev_device_unref(device); + if (!ret) + break; + } + ret = 0; + goto complete; + +error: + ret = -1; +complete: + if (enumerate) + udev_enumerate_unref(enumerate); + + if (udev) + udev_unref(udev); + + return ret; +} + +#if 0 +gboolean get_default_subdev_device(_v4l2src_data *data) { + struct media_device_info device_info = {0}; + struct media_v2_topology topology = {0}; + struct media_v2_entity *encoder_entity; + struct stat sb; + gboolean found = FALSE; + unsigned int major, minor; + struct media_entity_desc ent_desc; + + int media_fd = -1; + int video_fd = -1; + dev_t devnum; + int ret, i, j; + + media_fd = open(data->device, O_RDWR | O_NONBLOCK); + if (media_fd < 0) + return -errno; + + if (fstat(media_fd, &sb) == -1) { + fprintf(stderr, "failed to stat file\n"); + } + + ret = media_device_info(media_fd, &device_info); + if (ret) + return -errno; + + media_topology_get(media_fd, &topology); + + struct media_v2_entity v2_ents[topology.num_entities]; + struct media_v2_interface v2_ifaces[topology.num_interfaces]; + struct media_v2_pad v2_pads[topology.num_pads]; + struct media_v2_link v2_links[topology.num_links]; + + topology.ptr_entities = (__u64)v2_ents; + topology.ptr_interfaces = (__u64)v2_ifaces; + topology.ptr_pads = (__u64)v2_pads; + topology.ptr_links = (__u64)v2_links; + + ret = media_device_info(media_fd, &device_info); + if (ret) + goto merror; + + g_print("driver name: %s,bus: %s , model: %s, serial: %s\n", + (const gchar *)(device_info.driver),device_info.bus_info,device_info.model, device_info.serial); + + + ret = media_topology_get(media_fd, &topology); + if (ret) + goto merror; + + if (!topology.num_interfaces || !topology.num_entities || + !topology.num_pads || !topology.num_links) { + ret = -ENODEV; + goto merror; + } + + encoder_entity = media_topology_entity_find_by_function(&topology, + MEDIA_ENT_F_IO_V4L); + if (encoder_entity) { + g_print("encoder_entity name: %s, id: %d \n", + (const gchar *)(encoder_entity->name), encoder_entity->id); + } + + enumerate_entity_desc(media_fd,&topology); + + + const struct media_v2_interface *iface = &v2_ifaces[i]; + + for (i = 0; i < topology.num_links; i++) { + __u32 type = v2_links[i].flags & MEDIA_LNK_FL_LINK_TYPE; + + if (type != MEDIA_LNK_FL_INTERFACE_LINK) + continue; + if (v2_links[i].source_id == iface->id) + break; + } + g_print("interface Info: major: %d, minor: %d\n", iface->devnode.major,iface->devnode.minor); + const struct media_v2_entity *ent = &v2_ents[i]; + + g_print("Entity Info:\n"); + g_print("\tID : 0x%08x (%u)\n", ent->id, ent->id); + g_print("\tName : %s\n", ent->name); + + for (i = 0; i < topology.num_pads; i++) { + const struct media_v2_pad pad = v2_pads[i]; + + if (pad.entity_id != ent->id) + continue; + + for (int j = 0; j < topology.num_links;j++) { + __u32 type = v2_links[i].flags & MEDIA_LNK_FL_LINK_TYPE; + + if (type != MEDIA_LNK_FL_INTERFACE_LINK) + continue; + g_print("\tPad 0x%08x ,link ID 0x%08x \n", v2_pads[i].id,v2_links[j].id); + } + + for (int j = 0; j < topology.num_entities; j++) { + g_print("\tPad 0x%08x ,entities name: %s, \n", v2_pads[i].id, (const gchar *)(v2_ents[j].name)); + } + } + +merror: + if (media_fd >= 0) + close(media_fd); + + if (video_fd >= 0) + close(video_fd); +} + + +static get_i2c_media_device(_v4l2src_data *data) { + GList *videolist = NULL; + gboolean found = FALSE; + DIR *devdir; + struct dirent *dir; + devdir = opendir("/dev/"); + if (devdir) { + while ((dir = readdir(devdir)) != NULL) { + if (strlen(dir->d_name) > 5 && g_str_has_prefix(dir->d_name, "media")) { + videolist = g_list_append(videolist, g_strdup_printf("/dev/%s", dir->d_name)); + } + } + closedir(devdir); + } + + // find an video capture device. + for (GList *iter = videolist; iter != NULL; iter = iter->next) { + _v4l2src_data item; + item.device = iter->data; + item.width = data->width; + item.height = data->height; + item.format = data->format; + item.framerate = data->framerate; + if (find_video_device_fmt(&item, FALSE)) { + g_warning("found video capture : %s, but not match you video capture configuration!!!\n", (const gchar *)(iter->data)); + g_free(data->device); + data->device = g_strdup(iter->data); + found = TRUE; + goto found_dev; + } + } + + // find an default video capture settings. + + for (GList *iter = videolist; iter != NULL; iter = iter->next) { + _v4l2src_data item; + item.device = iter->data; + // if (get_default_subdev_device(&item)) { + if (0 == enumerate_udev_list(&item)){ + g_free(data->device); + data->device = g_strdup(iter->data); + data->width = item.width; + data->height = item.height; + data->framerate = item.framerate; + found = TRUE; + break; + } + } + +found_dev: + g_list_free_full(videolist, g_free); + return found; +} +#endif + gboolean get_capture_device(_v4l2src_data *data) { GList *videolist = NULL; gboolean found = FALSE; DIR *devdir; + struct dirent *dir; + devdir = opendir("/dev/"); - if(devdir) { - while((dir = readdir(devdir)) != NULL) { - if (strlen(dir->d_name) > 5 && g_str_has_prefix(dir->d_name, "video")) { + if (devdir) { + while ((dir = readdir(devdir)) != NULL) { + if (strlen(dir->d_name) > 5 && g_str_has_prefix(dir->d_name, "video")) { videolist = g_list_append(videolist, g_strdup_printf("/dev/%s", dir->d_name)); } } @@ -456,14 +1099,14 @@ gboolean get_capture_device(_v4l2src_data *data) { } // find an video capture device. - for (GList *iter = videolist; iter != NULL; iter = iter->next ){ + for (GList *iter = videolist; iter != NULL; iter = iter->next) { _v4l2src_data item; item.device = iter->data; item.width = data->width; item.height = data->height; item.format = data->format; item.framerate = data->framerate; - if (find_video_device_fmt(&item,FALSE)) { + if (find_video_device_fmt(&item, FALSE)) { g_warning("found video capture : %s, but not match you video capture configuration!!!\n", (const gchar *)(iter->data)); g_free(data->device); data->device = g_strdup(iter->data); @@ -490,6 +1133,12 @@ gboolean get_capture_device(_v4l2src_data *data) { found_dev: g_list_free_full(videolist, g_free); + if(!found) { + g_print("Not found , detect csi camera!!!\n"); + // get_i2c_media_device(data); + if(0 == enumerate_udev_list(data)) // another way by udev enumerate. + found = TRUE; + } return found; } @@ -508,17 +1157,16 @@ gboolean find_video_device_fmt(_v4l2src_data *data, const gboolean showdump) { fmtdesc.index = 0; fmtdesc.mbus_code = 0; // Open the device file - fd = open(data->device, O_RDWR); + fd = open(data->device, O_RDWR | O_NONBLOCK); if (fd < 0) { return match; } - if( 0 > ioctl(fd, VIDIOC_QUERYCAP, &capability)) - { + if (0 != device_cap_info(fd, &capability)) { goto no_match; } - - if (g_str_has_prefix((const gchar *)&capability.bus_info,"platform:")) { + g_print("1ioctl: cap info: %s\n", (const gchar *)(capability.bus_info)); + if (g_str_has_prefix((const gchar *)&capability.bus_info, "platform:")) { goto no_match; } @@ -526,7 +1174,7 @@ gboolean find_video_device_fmt(_v4l2src_data *data, const gboolean showdump) { struct media_device_info mdi; if (0 == ioctl(fd, MEDIA_IOC_DEVICE_INFO, &mdi)) { if (mdi.bus_info[0]) - g_print("ioctl: has bus_info[0]: %s\n",(const gchar *)(mdi.bus_info)); + g_print("ioctl: has bus_info[0]: %s\n", (const gchar *)(mdi.bus_info)); else g_print("ioctl: driver info: %s\n", (const gchar *)(mdi.driver)); } else { @@ -540,20 +1188,18 @@ gboolean find_video_device_fmt(_v4l2src_data *data, const gboolean showdump) { for (; 0 == ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsize); frmsize.index++) { if (frmsize.type != V4L2_FRMSIZE_TYPE_DISCRETE) continue; - if(frmsize.discrete.width == data->width && - frmsize.discrete.height == data->height) - { + if (frmsize.discrete.width == data->width && + frmsize.discrete.height == data->height) { frmval.pixel_format = fmtdesc.pixelformat; frmval.index = 0; frmval.width = frmsize.discrete.width; frmval.height = frmsize.discrete.height; for (; 0 == ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frmval); frmval.index++) { - gfloat fps = ((1.0 * frmval.discrete.denominator) / frmval.discrete.numerator); - if(data->framerate == (int)fps) - { - match = TRUE; - break; - } + gfloat fps = ((1.0 * frmval.discrete.denominator) / frmval.discrete.numerator); + if (data->framerate == (int)fps) { + match = TRUE; + break; + } } break; } diff --git a/v4l2ctl.h b/v4l2ctl.h index 139113e..e1b38ac 100644 --- a/v4l2ctl.h +++ b/v4l2ctl.h @@ -26,13 +26,17 @@ #include #include #include +#include + +#include #include #include #include #include -gchar *get_device_json(const gchar* device); -int set_ctrl_value(const gchar *device,int ctrl_id, int ctrl_val); +GHashTable *initial_capture_hashtable(); +gchar *get_device_json(const gchar *device); +int set_ctrl_value(const gchar *device, int ctrl_id, int ctrl_val); int reset_user_ctrls(const gchar *device); int dump_video_device_fmt(const gchar *device); gboolean find_video_device_fmt(_v4l2src_data *data, const gboolean showdump);