$ git clone https://tcclient.ion.nu/tc_client.git
commit 14de811d874cfd77cc75b035f58d1fc8c3de005b
Author: Alicia <...>
Date: Tue Dec 15 14:45:37 2015 +0100
libcamera: added support for a virtual "Image" camera.
diff --git a/ChangeLog b/ChangeLog
index ef90879..4c55abe 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,6 +1,7 @@
0.37:
Reimplemented announcement of moderators.
Reimplemented announcement of people cammed up when joining.
+libcamera: added support for a virtual "Image" camera.
0.36:
Implemented /whois <nick/ID> to check someone's username.
Changed the /whois output to be more human-readable in cases where the user isn't logged in.
diff --git a/Makefile b/Makefile
index 95f9e84..d93b3f7 100644
--- a/Makefile
+++ b/Makefile
@@ -8,9 +8,10 @@ endif
OBJ=client.o amfparser.o rtmp.o numlist.o amfwriter.o idlist.o colors.o endian.o media.o
IRCHACK_OBJ=utilities/irchack/irchack.o utilities/compat.o
MODBOT_OBJ=utilities/modbot/modbot.o utilities/list.o utilities/modbot/queue.o
-CAMVIEWER_OBJ=utilities/camviewer/camviewer.o utilities/compat.o
+CAMVIEWER_OBJ=utilities/camviewer/camviewer.o utilities/compat.o libcamera.a
CURSEDCHAT_OBJ=utilities/cursedchat/cursedchat.o utilities/cursedchat/buffer.o utilities/compat.o utilities/list.o
-TC_CLIENT_GTK_OBJ=utilities/gtk/camviewer.o utilities/gtk/userlist.o utilities/gtk/media.o utilities/gtk/compat.o utilities/gtk/config.o utilities/gtk/gui.o utilities/stringutils.o utilities/gtk/logging.o utilities/compat.o
+TC_CLIENT_GTK_OBJ=utilities/gtk/camviewer.o utilities/gtk/userlist.o utilities/gtk/media.o utilities/gtk/compat.o utilities/gtk/config.o utilities/gtk/gui.o utilities/stringutils.o utilities/gtk/logging.o utilities/compat.o libcamera.a
+LIBCAMERA_OBJ=utilities/libcamera/camera.o utilities/libcamera/camera_img.o
UTILS=irchack modbot
ifdef GTK_LIBS
ifdef AVCODEC_LIBS
@@ -34,10 +35,8 @@ ifdef SWSCALE_LIBS
@echo 'To build the core (tc_client.exe), enter this directory from cygwin and type make'
endif
ifdef LIBV4L2_LIBS
- CFLAGS+=-DHAVE_CAM $(LIBV4L2_CFLAGS)
+ CFLAGS+=-DHAVE_V4L2 $(LIBV4L2_CFLAGS)
LIBCAMERA_OBJ+=utilities/libcamera/camera_v4l2.o
- CAMVIEWER_OBJ+=libcamera.a
- TC_CLIENT_GTK_OBJ+=libcamera.a
endif
endif
endif
@@ -95,7 +94,7 @@ SOURCES+=utilities/camviewer/camviewer.c
SOURCES+=utilities/cursedchat/cursedchat.c utilities/cursedchat/buffer.c utilities/cursedchat/buffer.h
SOURCES+=utilities/gtk/camviewer.c utilities/gtk/userlist.c utilities/gtk/media.c utilities/gtk/compat.c utilities/gtk/config.c utilities/gtk/gui.c utilities/gtk/logging.c utilities/gtk/userlist.h utilities/gtk/media.h utilities/gtk/compat.h utilities/gtk/config.h utilities/gtk/gui.h utilities/gtk/logging.h gtkgui.glade
SOURCES+=utilities/compat.c utilities/compat.h utilities/list.c utilities/list.h utilities/stringutils.c utilities/stringutils.h
-SOURCES+=utilities/libcamera/camera.h utilities/libcamera/camera_v4l2.c
+SOURCES+=utilities/libcamera/camera.c utilities/libcamera/camera.h utilities/libcamera/camera_v4l2.c utilities/libcamera/camera_img.c
tarball:
tar -cJf tc_client-$(VERSION).tar.xz --transform='s|^|tc_client-$(VERSION)/|' $(SOURCES)
diff --git a/utilities/camviewer/camviewer.c b/utilities/camviewer/camviewer.c
index 73fb318..1df934a 100644
--- a/utilities/camviewer/camviewer.c
+++ b/utilities/camviewer/camviewer.c
@@ -41,7 +41,7 @@
#include <ao/ao.h>
#endif
#include <gtk/gtk.h>
-#ifdef HAVE_CAM
+#ifndef _WIN32
#include "../libcamera/camera.h"
#endif
#include "../compat.h"
@@ -390,7 +390,7 @@ void audiothread(int fd)
}
#endif
-#ifdef HAVE_CAM
+#ifndef _WIN32
pid_t camproc=0;
void togglecam(GtkButton* button, struct viddata* data)
{
@@ -521,7 +521,7 @@ int main(int argc, char** argv)
g_signal_connect(w, "destroy", gtk_main_quit, 0);
data.box=gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
gtk_container_add(GTK_CONTAINER(w), data.box);
-#ifdef HAVE_CAM
+#ifndef _WIN32
data.vencoder=avcodec_find_encoder(AV_CODEC_ID_FLV1);
GtkWidget* cambutton=gtk_button_new_with_label("Broadcast cam");
g_signal_connect(cambutton, "clicked", G_CALLBACK(togglecam), &data);
diff --git a/utilities/gtk/camviewer.c b/utilities/gtk/camviewer.c
index f51be67..3636ea8 100644
--- a/utilities/gtk/camviewer.c
+++ b/utilities/gtk/camviewer.c
@@ -45,9 +45,7 @@
#endif
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
-#ifdef HAVE_CAM
- #include "../libcamera/camera.h"
-#endif
+#include "../libcamera/camera.h"
#include "userlist.h"
#include "media.h"
#include "compat.h"
@@ -599,7 +597,6 @@ void audiothread(int fd)
}
#endif
-#ifdef HAVE_CAM
unsigned int cameventsource=0;
void togglecam(GtkCheckMenuItem* item, struct viddata* data)
{
@@ -623,7 +620,6 @@ void togglecam(GtkCheckMenuItem* item, struct viddata* data)
GIOChannel* channel=camthread(gtk_combo_box_get_active_id(combo), data->vencoder, 100000);
cameventsource=g_io_add_watch(channel, G_IO_IN, handledata, 0);
}
-#endif
gboolean handleresize(GtkWidget* widget, GdkEventConfigure* event, struct viddata* data)
{
@@ -929,7 +925,6 @@ int main(int argc, char** argv)
gtk_builder_connect_signals(gui, 0);
unsigned int i;
-#ifdef HAVE_CAM
GtkWidget* item=GTK_WIDGET(gtk_builder_get_object(gui, "menuitem_broadcast_camera"));
g_signal_connect(item, "toggled", G_CALLBACK(togglecam), data);
data->vencoder=avcodec_find_encoder(AV_CODEC_ID_FLV1);
@@ -949,6 +944,8 @@ int main(int argc, char** argv)
// Signals for switching from preview to streaming
item=GTK_WIDGET(gtk_builder_get_object(gui, "camselect_ok"));
g_signal_connect(item, "clicked", G_CALLBACK(camselect_accept), data->vencoder);
+ // Enable the "img" camera
+ cam_img_filepicker=camselect_file;
// Populate list of cams
unsigned int count;
char** cams=cam_list(&count);
@@ -962,10 +959,6 @@ int main(int argc, char** argv)
gtk_combo_box_set_model(combo, GTK_TREE_MODEL(list));
g_object_unref(list);
gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), gtk_cell_renderer_text_new(), 1);
-#else
- GtkWidget* item=GTK_WIDGET(gtk_builder_get_object(gui, "menuitem_broadcast"));
- gtk_widget_destroy(item);
-#endif
item=GTK_WIDGET(gtk_builder_get_object(gui, "menuitem_options_settings"));
g_signal_connect(item, "activate", G_CALLBACK(showsettings), gui);
diff --git a/utilities/gtk/media.c b/utilities/gtk/media.c
index 60fe39c..d254445 100644
--- a/utilities/gtk/media.c
+++ b/utilities/gtk/media.c
@@ -18,16 +18,16 @@
#include <string.h>
#include <gtk/gtk.h>
#include <libavcodec/avcodec.h>
-#ifdef HAVE_CAM
- #include <sys/prctl.h>
- #include <libswscale/swscale.h>
- #if LIBAVUTIL_VERSION_MAJOR>50 || (LIBAVUTIL_VERSION_MAJOR==50 && LIBAVUTIL_VERSION_MINOR>37)
- #include <libavutil/imgutils.h>
- #else
- #include <libavcore/imgutils.h>
- #endif
- #include "../libcamera/camera.h"
+#ifndef _WIN32
+#include <sys/prctl.h>
#endif
+#include <libswscale/swscale.h>
+#if LIBAVUTIL_VERSION_MAJOR>50 || (LIBAVUTIL_VERSION_MAJOR==50 && LIBAVUTIL_VERSION_MINOR>37)
+ #include <libavutil/imgutils.h>
+#else
+ #include <libavcore/imgutils.h>
+#endif
+#include "../libcamera/camera.h"
#include "../compat.h"
#include "compat.h"
#include "gui.h"
@@ -155,7 +155,11 @@ void camera_cleanup(void)
free(cams);
}
-#ifdef HAVE_CAM
+unsigned int* camthread_delay;
+void camthread_resetdelay(int x)
+{
+ *camthread_delay=500000;
+}
extern unsigned int cameventsource;
GIOChannel* camthread(const char* name, AVCodec* vencoder, unsigned int delay)
{
@@ -166,15 +170,18 @@ GIOChannel* camthread(const char* name, AVCodec* vencoder, unsigned int delay)
usleep(200000); // Give the previous process some time to shut down
}
// Set up a second pipe to be handled by handledata() to avoid overlap with tc_client's output
+ CAM* cam=cam_open(name); // Opening here in case of GUI callbacks
int campipe[2];
pipe(campipe);
camproc=fork();
if(!camproc)
{
close(campipe[0]);
+ if(!cam){_exit(1);}
prctl(PR_SET_PDEATHSIG, SIGHUP);
+ camthread_delay=&delay;
+ signal(SIGUSR1, camthread_resetdelay);
// Set up camera
- CAM* cam=cam_open(name);
AVCodecContext* ctx=avcodec_alloc_context3(vencoder);
ctx->width=320;
ctx->height=240;
@@ -226,6 +233,7 @@ GIOChannel* camthread(const char* name, AVCodec* vencoder, unsigned int delay)
sws_freeContext(swsctx);
_exit(0);
}
+ if(cam){cam_close(cam);} // Leave the cam to the child process
close(campipe[1]);
GIOChannel* channel=g_io_channel_unix_new(campipe[0]);
g_io_channel_set_encoding(channel, 0, 0);
@@ -255,10 +263,38 @@ void camselect_accept(GtkWidget* widget, AVCodec* vencoder)
{
GtkWidget* window=GTK_WIDGET(gtk_builder_get_object(gui, "camselection"));
gtk_widget_hide(window);
- // Restart the cam thread with a high initial delay to workaround the bug in the flash client
- GtkComboBox* combo=GTK_COMBO_BOX(gtk_builder_get_object(gui, "camselect_combo"));
- GIOChannel* channel=camthread(gtk_combo_box_get_active_id(combo), vencoder, 500000);
- cameventsource=g_io_add_watch(channel, G_IO_IN, (void*)&handledata, 0);
+ // Tell the camthread to reset the delay to 500000 as a workaround for a quirk in the flash client
+ kill(camproc, SIGUSR1);
dprintf(tc_client_in[1], "/camup\n");
}
-#endif
+
+void camselect_file_preview(GtkFileChooser* dialog, gpointer data)
+{
+ GtkImage* preview=GTK_IMAGE(data);
+ char* file=gtk_file_chooser_get_preview_filename(dialog);
+ GdkPixbuf* img=gdk_pixbuf_new_from_file_at_size(file, 256, 256, 0);
+ g_free(file);
+ gtk_image_set_from_pixbuf(preview, img);
+ if(img){g_object_unref(img);}
+ gtk_file_chooser_set_preview_widget_active(dialog, !!img);
+}
+
+const char* camselect_file(void)
+{
+ GtkWidget* preview=gtk_image_new();
+ GtkWindow* window=GTK_WINDOW(gtk_builder_get_object(gui, "camselection"));
+ GtkWidget* dialog=gtk_file_chooser_dialog_new("Open Image", window, GTK_FILE_CHOOSER_ACTION_OPEN, "_Cancel", GTK_RESPONSE_CANCEL, "_Open", GTK_RESPONSE_ACCEPT, (char*)0);
+ GtkFileFilter* filter=gtk_file_filter_new();
+ gtk_file_filter_add_pixbuf_formats(filter);
+ gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
+ gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog), preview);
+ g_signal_connect(dialog, "update-preview", G_CALLBACK(camselect_file_preview), preview);
+ int res=gtk_dialog_run(GTK_DIALOG(dialog));
+ char* file;
+ if(res==GTK_RESPONSE_ACCEPT)
+ {
+ file=gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
+ }else{file=0;}
+ gtk_widget_destroy(dialog);
+ return file;
+}
diff --git a/utilities/gtk/media.h b/utilities/gtk/media.h
index 1824e80..8b2db74 100644
--- a/utilities/gtk/media.h
+++ b/utilities/gtk/media.h
@@ -42,9 +42,8 @@ extern struct camera* camera_find(const char* id);
extern struct camera* camera_findbynick(const char* nick);
extern struct camera* camera_new(void);
extern void camera_cleanup(void);
-#ifdef HAVE_CAM
extern GIOChannel* camthread(const char* name, AVCodec* vencoder, unsigned int delay);
extern void camselect_change(GtkComboBox* combo, AVCodec* vencoder);
extern gboolean camselect_cancel(GtkWidget* widget, void* x1, void* x2);
extern void camselect_accept(GtkWidget* widget, AVCodec* vencoder);
-#endif
+extern const char* camselect_file(void);
diff --git a/utilities/libcamera/camera.c b/utilities/libcamera/camera.c
new file mode 100644
index 0000000..b005693
--- /dev/null
+++ b/utilities/libcamera/camera.c
@@ -0,0 +1,74 @@
+/*
+ libcamera, a camera access abstraction library
+ Copyright (C) 2015 alicia@ion.nu
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, version 3 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+#include "camera.h"
+
+struct CAM_t
+{
+ unsigned int type;
+};
+
+char** cam_list(unsigned int* count)
+{
+ char** list=0;
+ *count=0;
+ #ifdef HAVE_V4L2
+ list=cam_list_v4l2(list, count);
+ #endif
+ list=cam_list_img(list, count);
+ return list;
+}
+
+CAM* cam_open(const char* name)
+{
+ #ifdef HAVE_V4L2
+ if(!strncmp(name, "v4l2:", 5)){return cam_open_v4l2(name);}
+ #endif
+ if(!strcmp(name, "Image")){return cam_open_img();}
+}
+
+void cam_resolution(CAM* cam, unsigned int* width, unsigned int* height)
+{
+ switch(cam->type)
+ {
+ #ifdef HAVE_V4L2
+ case CAMTYPE_V4L2: cam_resolution_v4l2(cam, width, height); break;
+ #endif
+ case CAMTYPE_IMG: cam_resolution_img(cam, width, height); break;
+ }
+}
+
+void cam_getframe(CAM* cam, void* pixmap)
+{
+ switch(cam->type)
+ {
+ #ifdef HAVE_V4L2
+ case CAMTYPE_V4L2: cam_getframe_v4l2(cam, pixmap); break;
+ #endif
+ case CAMTYPE_IMG: cam_getframe_img(cam, pixmap); break;
+ }
+}
+
+void cam_close(CAM* cam)
+{
+ switch(cam->type)
+ {
+ #ifdef HAVE_V4L2
+ case CAMTYPE_V4L2: cam_close_v4l2(cam); break;
+ #endif
+ case CAMTYPE_IMG: cam_close_img(cam); break;
+ }
+}
diff --git a/utilities/libcamera/camera.h b/utilities/libcamera/camera.h
index 0d3f9f6..049705d 100644
--- a/utilities/libcamera/camera.h
+++ b/utilities/libcamera/camera.h
@@ -14,6 +14,10 @@
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+enum{
+ CAMTYPE_V4L2,
+ CAMTYPE_IMG
+};
struct CAM_t;
typedef struct CAM_t CAM;
extern char** cam_list(unsigned int* count);
@@ -22,3 +26,4 @@ extern CAM* cam_open(const char* name);
extern void cam_resolution(CAM* cam, unsigned int* width, unsigned int* height);
extern void cam_getframe(CAM* cam, void* pixmap);
extern void cam_close(CAM* cam);
+extern const char*(*cam_img_filepicker)(void);
diff --git a/utilities/libcamera/camera_img.c b/utilities/libcamera/camera_img.c
new file mode 100644
index 0000000..646bcd1
--- /dev/null
+++ b/utilities/libcamera/camera_img.c
@@ -0,0 +1,113 @@
+/*
+ libcamera, a camera access abstraction library
+ Copyright (C) 2015 alicia@ion.nu
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, version 3 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+#include <stdlib.h>
+#include <string.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include "camera.h"
+
+typedef struct CAM_t
+{
+ unsigned int type;
+ GdkPixbufAnimation* anim;
+ GdkPixbuf* staticimg;
+ GdkPixbufAnimationIter* iter;
+} CAM;
+
+const char*(*cam_img_filepicker)(void)=0;
+
+void cam_img_fixpixels_raw(unsigned char* pixels, unsigned char* from, unsigned int pixelcount)
+{
+ unsigned int i;
+ for(i=0; i<pixelcount; ++i)
+ {
+ memmove(&pixels[i*3], &from[i*4], 3);
+ }
+}
+
+void cam_img_fixpixels(GdkPixbuf* img)
+{
+ if(gdk_pixbuf_get_n_channels(img)==4)
+ {
+ unsigned char* pixels=gdk_pixbuf_get_pixels(img);
+ unsigned int pixelcount=gdk_pixbuf_get_width(img)*gdk_pixbuf_get_height(img);
+ cam_img_fixpixels_raw(pixels, pixels, pixelcount);
+ }
+}
+
+char** cam_list_img(char** list, unsigned int* count)
+{
+ if(!cam_img_filepicker){return list;} // Don't give the option if we don't have the required callback (toolkit-agnostic)
+ ++*count;
+ list=realloc(list, sizeof(char*)*(*count));
+ list[(*count)-1]=strdup("Image");
+ return list;
+}
+
+CAM* cam_open_img(void)
+{
+ if(!cam_img_filepicker){return 0;}
+ const char* file=cam_img_filepicker();
+ if(!file){return 0;}
+ CAM* cam=malloc(sizeof(CAM));
+ cam->type=CAMTYPE_IMG;
+ cam->anim=gdk_pixbuf_animation_new_from_file(file, 0);
+ if(gdk_pixbuf_animation_is_static_image(cam->anim))
+ {
+ cam->staticimg=gdk_pixbuf_animation_get_static_image(cam->anim);
+ cam_img_fixpixels(cam->staticimg);
+ cam->iter=0;
+ }else{
+ cam->staticimg=0;
+ cam->iter=gdk_pixbuf_animation_get_iter(cam->anim, 0);
+ }
+ return cam;
+}
+
+void cam_resolution_img(CAM* cam, unsigned int* width, unsigned int* height)
+{
+ // TODO: Implement scaling of images?
+ GdkPixbuf* img;
+ if(cam->staticimg){img=cam->staticimg;}
+ else{img=gdk_pixbuf_animation_iter_get_pixbuf(cam->iter);}
+ *width=gdk_pixbuf_get_width(img);
+ *height=gdk_pixbuf_get_height(img);
+}
+
+void cam_getframe_img(CAM* cam, void* pixmap)
+{
+ GdkPixbuf* img;
+ if(cam->staticimg)
+ {
+ img=cam->staticimg;
+ }else{
+ gdk_pixbuf_animation_iter_advance(cam->iter, 0);
+ img=gdk_pixbuf_animation_iter_get_pixbuf(cam->iter);
+ }
+ void* pixels=gdk_pixbuf_get_pixels(img);
+ if(cam->iter && gdk_pixbuf_get_n_channels(img)==4) // Inefficient, but we don't get to fix the pixbuf itself and mark it as fixed, at least not with any of the ways I've tried
+ {
+ cam_img_fixpixels_raw(pixmap, pixels, gdk_pixbuf_get_width(img)*gdk_pixbuf_get_height(img));
+ }else{
+ memcpy(pixmap, pixels, gdk_pixbuf_get_width(img)*3*gdk_pixbuf_get_height(img));
+ }
+}
+
+void cam_close_img(CAM* cam)
+{
+ g_object_unref(G_OBJECT(cam->anim));
+ free(cam);
+}
diff --git a/utilities/libcamera/camera_v4l2.c b/utilities/libcamera/camera_v4l2.c
index 3a488e3..8bbee7d 100644
--- a/utilities/libcamera/camera_v4l2.c
+++ b/utilities/libcamera/camera_v4l2.c
@@ -25,14 +25,13 @@
typedef struct CAM_t
{
+ unsigned int type;
int fd;
unsigned int framesize;
} CAM;
-char** cam_list(unsigned int* count)
+char** cam_list_v4l2(char** list, unsigned int* count)
{
- char** list=0;
- *count=0;
DIR* dir=opendir("/dev");
struct dirent* entry;
while((entry=readdir(dir)))
@@ -41,22 +40,23 @@ char** cam_list(unsigned int* count)
{
++*count;
list=realloc(list, sizeof(char*)*(*count));
- char* path=malloc(strlen("/dev/0")+strlen(entry->d_name));
- sprintf(path, "/dev/%s", entry->d_name);
+ char* path=malloc(strlen("v4l2:/dev/0")+strlen(entry->d_name));
+ sprintf(path, "v4l2:/dev/%s", entry->d_name);
list[(*count)-1]=path;
}
}
return list;
}
-CAM* cam_open(const char* name)
+CAM* cam_open_v4l2(const char* name)
{
CAM* cam=malloc(sizeof(CAM));
- cam->fd=v4l2_open(name, O_RDWR);
+ cam->type=CAMTYPE_V4L2;
+ cam->fd=v4l2_open(&name[5], O_RDWR);
return cam;
}
-void cam_resolution(CAM* cam, unsigned int* width, unsigned int* height)
+void cam_resolution_v4l2(CAM* cam, unsigned int* width, unsigned int* height)
{
struct v4l2_format fmt;
fmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
@@ -73,12 +73,12 @@ void cam_resolution(CAM* cam, unsigned int* width, unsigned int* height)
cam->framesize=(*width)*(*height)*3;
}
-void cam_getframe(CAM* cam, void* pixmap)
+void cam_getframe_v4l2(CAM* cam, void* pixmap)
{
v4l2_read(cam->fd, pixmap, cam->framesize);
}
-void cam_close(CAM* cam)
+void cam_close_v4l2(CAM* cam)
{
v4l2_close(cam->fd);
free(cam);