$ git clone http://tcclient.ion.nu/tc_client.git
commit b421fd7a948554773f9f2153acdb0fb7bc027a66
Author: Alicia <...>
Date:   Tue Jun 30 10:30:54 2015 +0200

    tc_client-gtk and camviewer: added an abstraction library (libcamera) for cam access.

diff --git a/ChangeLog b/ChangeLog
index f6916a2..95f88d9 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -4,6 +4,7 @@ Print version info when called with -v/--version (contributed by Jade)
 Flush stdout after printing unknown RTMP types, and return upon <1 reads in b_read.
 tc_client-gtk: fixed windows compatibility (w32_runcmd and strndup)
 tc_client-gtk and camviewer: added some compatibility macros for older libav versions.
+tc_client-gtk and camviewer: added an abstraction library (libcamera) for cam access.
 modbot: added an option (--disable-lists) to disable playlist requests, instead only the first video linked to will be added to the queue.
 modbot: make a note about skipped videos (!skip) not being marked as bad, and list the skipped videos to make it easier if they were meant to be marked as bad.
 modbot: give feedback for !wrongrequest, and if the user is a mod and the dropped video was the last one approved, remove it from goodvids since it was probably requested (and automatically approved) by accident.
diff --git a/Makefile b/Makefile
index 5588e9c..f145209 100644
--- a/Makefile
+++ b/Makefile
@@ -27,15 +27,18 @@ ifdef SWSCALE_LIBS
       CFLAGS+=-DHAVE_SWRESAMPLE=1 $(SWRESAMPLE_CFLAGS) $(AO_CFLAGS)
     endif
   endif
-  ifdef LIBV4L2_LIBS
-    CFLAGS+=-DHAVE_V4L2 $(LIBV4L2_CFLAGS)
-  endif
   ifneq ($(findstring MINGW,$(shell uname -s)),)
     LDFLAGS+=-mwindows
     windowstargets: camviewer tc_client-gtk
  @echo
  @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)
+    LIBCAMERA_OBJ+=utilities/libcamera/camera_v4l2.o
+    CAMVIEWER_OBJ+=libcamera.a
+    TC_CLIENT_GTK_OBJ+=libcamera.a
+  endif
 endif
 endif
 endif
@@ -47,6 +50,12 @@ ifdef READLINE_LIBS
   INSTALLDEPS+=cursedchat
 endif
 endif
+ifeq ($(AR),)
+  AR=ar
+endif
+ifeq ($(RANLIB),)
+  RANLIB=ranlib
+endif
 CFLAGS+=-DPREFIX=\"$(PREFIX)\" -DVERSION=\"$(VERSION)\"
 INSTALLDEPS=tc_client
 
@@ -70,8 +79,12 @@ cursedchat: $(CURSEDCHAT_OBJ)
 tc_client-gtk: $(TC_CLIENT_GTK_OBJ)
  $(CC) $(LDFLAGS) $^ $(LIBS) $(GTK_LIBS) $(AVCODEC_LIBS) $(AVUTIL_LIBS) $(SWSCALE_LIBS) $(AVRESAMPLE_LIBS) $(SWRESAMPLE_LIBS) $(AO_LIBS) $(LIBV4L2_LIBS) -o $@
 
+libcamera.a: $(LIBCAMERA_OBJ)
+ $(AR) cru $@ $^
+ $(RANLIB) $@
+
 clean:
- rm -f $(OBJ) $(IRCHACK_OBJ) $(MODBOT_OBJ) $(CAMVIEWER_OBJ) $(CURSEDCHAT_OBJ) $(TC_CLIENT_GTK_OBJ) tc_client irchack modbot camviewer cursedchat tc_client-gtk
+ rm -f $(OBJ) $(IRCHACK_OBJ) $(MODBOT_OBJ) $(CAMVIEWER_OBJ) $(CURSEDCHAT_OBJ) $(TC_CLIENT_GTK_OBJ) $(LIBCAMERA_OBJ) tc_client irchack modbot camviewer cursedchat tc_client-gtk
 
 SOURCES=Makefile client.c amfparser.c rtmp.c numlist.c amfwriter.c idlist.c colors.c endian.c media.c amfparser.h rtmp.h numlist.h amfwriter.h idlist.h colors.h endian.h media.h LICENSE README ChangeLog crossbuild.sh testbuilds.sh configure
 SOURCES+=utilities/irchack/irchack.c
@@ -80,6 +93,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
 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 9bbe689..6a72ea4 100644
--- a/utilities/camviewer/camviewer.c
+++ b/utilities/camviewer/camviewer.c
@@ -41,9 +41,8 @@
   #include <ao/ao.h>
 #endif
 #include <gtk/gtk.h>
-#ifdef HAVE_V4L2
-  #include <libv4l2.h>
-  #include <linux/videodev2.h>
+#ifdef HAVE_CAM
+  #include "../libcamera/camera.h"
 #endif
 #include "../compat.h"
 
@@ -370,7 +369,7 @@ void audiothread(int fd)
 }
 #endif
 
-#ifdef HAVE_V4L2
+#ifdef HAVE_CAM
 pid_t camproc=0;
 void togglecam(GtkButton* button, struct viddata* data)
 {
@@ -383,6 +382,9 @@ void togglecam(GtkButton* button, struct viddata* data)
     dprintf(tc_client[1], "VideoEnd: out\n"); // Close our local display
     return;
   }
+  unsigned int count;
+  char** cams=cam_list(&count);
+  if(!count){printf("No camera found\n"); return;}
   // Set up a second pipe to be handled by handledata() to avoid overlap with tc_client's output
   int campipe[2];
   pipe(campipe);
@@ -396,27 +398,19 @@ void togglecam(GtkButton* button, struct viddata* data)
     prctl(PR_SET_PDEATHSIG, SIGHUP);
     unsigned int delay=500000;
     // Set up camera
-    int fd=v4l2_open("/dev/video0", O_RDWR);
-    struct v4l2_format fmt;
-    fmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
-    fmt.fmt.pix.width=320;
-    fmt.fmt.pix.height=240;
-    fmt.fmt.pix.pixelformat=V4L2_PIX_FMT_RGB24;
-    fmt.fmt.pix.field=V4L2_FIELD_NONE;
-    fmt.fmt.pix.bytesperline=fmt.fmt.pix.width*3;
-    fmt.fmt.pix.sizeimage=fmt.fmt.pix.bytesperline*fmt.fmt.pix.height;
-    v4l2_ioctl(fd, VIDIOC_S_FMT, &fmt);
+    CAM* cam=cam_open(cams[0]);
     AVCodecContext* ctx=avcodec_alloc_context3(data->vencoder);
-    ctx->width=fmt.fmt.pix.width;
-    ctx->height=fmt.fmt.pix.height;
+    ctx->width=320;
+    ctx->height=240;
+    cam_resolution(cam, (unsigned int*)&ctx->width, (unsigned int*)&ctx->height);
     ctx->pix_fmt=PIX_FMT_YUV420P;
     ctx->time_base.num=1;
     ctx->time_base.den=10;
     avcodec_open2(ctx, data->vencoder, 0);
     AVFrame* frame=av_frame_alloc();
     frame->format=PIX_FMT_RGB24;
-    frame->width=fmt.fmt.pix.width;
-    frame->height=fmt.fmt.pix.height;
+    frame->width=ctx->width;
+    frame->height=ctx->height;
     av_image_alloc(frame->data, frame->linesize, ctx->width, ctx->height, frame->format, 1);
     AVPacket packet;
     packet.buf=0;
@@ -438,7 +432,7 @@ void togglecam(GtkButton* button, struct viddata* data)
     {
       usleep(delay);
       if(delay>100000){delay-=50000;}
-      v4l2_read(fd, frame->data[0], fmt.fmt.pix.sizeimage);
+      cam_getframe(cam, frame->data[0]);
       int gotpacket;
       sws_scale(swsctx, (const uint8_t*const*)frame->data, frame->linesize, 0, frame->height, dstframe->data, dstframe->linesize);
       av_init_packet(&packet);
@@ -506,7 +500,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_V4L2
+#ifdef HAVE_CAM
   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 ac1ba7d..29d049d 100644
--- a/utilities/gtk/camviewer.c
+++ b/utilities/gtk/camviewer.c
@@ -45,9 +45,8 @@
 #endif
 #include <gtk/gtk.h>
 #include <gdk/gdkkeysyms.h>
-#ifdef HAVE_V4L2
-  #include <libv4l2.h>
-  #include <linux/videodev2.h>
+#ifdef HAVE_CAM
+  #include "../libcamera/camera.h"
 #endif
 #include "userlist.h"
 #include "media.h"
@@ -577,7 +576,7 @@ void audiothread(int fd)
 }
 #endif
 
-#ifdef HAVE_V4L2
+#ifdef HAVE_CAM
 pid_t camproc=0;
 unsigned int cameventsource;
 void togglecam(GtkCheckMenuItem* item, struct viddata* data)
@@ -591,6 +590,9 @@ void togglecam(GtkCheckMenuItem* item, struct viddata* data)
     dprintf(tc_client[1], "VideoEnd: out\n"); // Close our local display
     return;
   }
+  unsigned int count;
+  char** cams=cam_list(&count);
+  if(!count){printf("No camera found\n"); return;}
   // Set up a second pipe to be handled by handledata() to avoid overlap with tc_client's output
   int campipe[2];
   pipe(campipe);
@@ -603,27 +605,19 @@ void togglecam(GtkCheckMenuItem* item, struct viddata* data)
     prctl(PR_SET_PDEATHSIG, SIGHUP);
     unsigned int delay=500000;
     // Set up camera
-    int fd=v4l2_open("/dev/video0", O_RDWR);
-    struct v4l2_format fmt;
-    fmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
-    fmt.fmt.pix.width=320;
-    fmt.fmt.pix.height=240;
-    fmt.fmt.pix.pixelformat=V4L2_PIX_FMT_RGB24;
-    fmt.fmt.pix.field=V4L2_FIELD_NONE;
-    fmt.fmt.pix.bytesperline=fmt.fmt.pix.width*3;
-    fmt.fmt.pix.sizeimage=fmt.fmt.pix.bytesperline*fmt.fmt.pix.height;
-    v4l2_ioctl(fd, VIDIOC_S_FMT, &fmt);
+    CAM* cam=cam_open(cams[0]);
     AVCodecContext* ctx=avcodec_alloc_context3(data->vencoder);
-    ctx->width=fmt.fmt.pix.width;
-    ctx->height=fmt.fmt.pix.height;
+    ctx->width=320;
+    ctx->height=240;
+    cam_resolution(cam, (unsigned int*)&ctx->width, (unsigned int*)&ctx->height);
     ctx->pix_fmt=PIX_FMT_YUV420P;
     ctx->time_base.num=1;
     ctx->time_base.den=10;
     avcodec_open2(ctx, data->vencoder, 0);
     AVFrame* frame=av_frame_alloc();
     frame->format=PIX_FMT_RGB24;
-    frame->width=fmt.fmt.pix.width;
-    frame->height=fmt.fmt.pix.height;
+    frame->width=ctx->width;
+    frame->height=ctx->height;
     av_image_alloc(frame->data, frame->linesize, ctx->width, ctx->height, frame->format, 1);
     AVPacket packet;
     packet.buf=0;
@@ -645,7 +639,7 @@ void togglecam(GtkCheckMenuItem* item, struct viddata* data)
     {
       usleep(delay);
       if(delay>100000){delay-=50000;}
-      v4l2_read(fd, frame->data[0], fmt.fmt.pix.sizeimage);
+      cam_getframe(cam, frame->data[0]);
       int gotpacket;
       sws_scale(swsctx, (const uint8_t*const*)frame->data, frame->linesize, 0, frame->height, dstframe->data, dstframe->linesize);
       av_init_packet(&packet);
@@ -967,7 +961,7 @@ int main(int argc, char** argv)
   }
   gtk_builder_connect_signals(gui, 0);
 
-#ifdef HAVE_V4L2
+#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);
diff --git a/utilities/libcamera/camera.h b/utilities/libcamera/camera.h
new file mode 100644
index 0000000..0d3f9f6
--- /dev/null
+++ b/utilities/libcamera/camera.h
@@ -0,0 +1,24 @@
+/*
+    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/>.
+*/
+struct CAM_t;
+typedef struct CAM_t CAM;
+extern char** cam_list(unsigned int* count);
+extern CAM* cam_open(const char* name);
+// Note: cam_resolution both tries to set the resolution and gets what it set it to
+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);
diff --git a/utilities/libcamera/camera_v4l2.c b/utilities/libcamera/camera_v4l2.c
new file mode 100644
index 0000000..3a488e3
--- /dev/null
+++ b/utilities/libcamera/camera_v4l2.c
@@ -0,0 +1,85 @@
+/*
+    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 <dirent.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <libv4l2.h>
+#include <linux/videodev2.h>
+#include "camera.h"
+
+typedef struct CAM_t
+{
+  int fd;
+  unsigned int framesize;
+} CAM;
+
+char** cam_list(unsigned int* count)
+{
+  char** list=0;
+  *count=0;
+  DIR* dir=opendir("/dev");
+  struct dirent* entry;
+  while((entry=readdir(dir)))
+  {
+    if(!strncmp(entry->d_name, "video", 5))
+    {
+      ++*count;
+      list=realloc(list, sizeof(char*)*(*count));
+      char* path=malloc(strlen("/dev/0")+strlen(entry->d_name));
+      sprintf(path, "/dev/%s", entry->d_name);
+      list[(*count)-1]=path;
+    }
+  }
+  return list;
+}
+
+CAM* cam_open(const char* name)
+{
+  CAM* cam=malloc(sizeof(CAM));
+  cam->fd=v4l2_open(name, O_RDWR);
+  return cam;
+}
+
+void cam_resolution(CAM* cam, unsigned int* width, unsigned int* height)
+{
+  struct v4l2_format fmt;
+  fmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
+  fmt.fmt.pix.width=*width;
+  fmt.fmt.pix.height=*height;
+  fmt.fmt.pix.pixelformat=V4L2_PIX_FMT_RGB24;
+  fmt.fmt.pix.field=V4L2_FIELD_NONE;
+  fmt.fmt.pix.bytesperline=fmt.fmt.pix.width*3;
+  fmt.fmt.pix.sizeimage=fmt.fmt.pix.bytesperline*fmt.fmt.pix.height;
+  v4l2_ioctl(cam->fd, VIDIOC_S_FMT, &fmt);
+  v4l2_ioctl(cam->fd, VIDIOC_G_FMT, &fmt);
+  *width=fmt.fmt.pix.width;
+  *height=fmt.fmt.pix.height;
+  cam->framesize=(*width)*(*height)*3;
+}
+
+void cam_getframe(CAM* cam, void* pixmap)
+{
+  v4l2_read(cam->fd, pixmap, cam->framesize);
+}
+
+void cam_close(CAM* cam)
+{
+  v4l2_close(cam->fd);
+  free(cam);
+}