diff --git a/photobooth.c b/photobooth.c new file mode 100644 index 0000000..bee2339 --- /dev/null +++ b/photobooth.c @@ -0,0 +1,230 @@ +/* + * GStreamer photobooth.c + * Copyright 2016 Andreas Frisch + * + * This program is licensed under the Creative Commons + * Attribution-NonCommercial-ShareAlike 3.0 Unported + * License. To view a copy of this license, visit + * http://creativecommons.org/licenses/by-nc-sa/3.0/ or send a letter to + * Creative Commons,559 Nathan Abbott Way,Stanford,California 94305,USA. + * + * This program is NOT free software. It is open source, you are allowed + * to modify it (if you keep the license), but it may not be commercially + * distributed other than under the conditions noted above. + */ + +// gcc -Wall -g `pkg-config gstreamer-1.0 gstreamer-video-1.0 libgphoto2 --cflags --libs gtk+-3.0 gtk+-x11-3.0` photobooth.c -o photobooth + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "photobooth.h" + +#define photo_booth_parent_class parent_class +G_DEFINE_TYPE (PhotoBooth, photo_booth, G_TYPE_OBJECT); + +GST_DEBUG_CATEGORY_STATIC (photo_booth_debug); +#define GST_CAT_DEFAULT photo_booth_debug + +gboolean _pb_cam_init (CameraInfo *cam_info); +gboolean _pb_cam_close (CameraInfo *cam_info); +void _pb_flush_pipe (int fd); +static void _pb_video_capture_thread_func (PhotoBooth *pb); + +static void photo_booth_finalize (GObject * object); + +static void photo_booth_class_init (PhotoBoothClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GST_DEBUG_CATEGORY_INIT (photo_booth_debug, "photobooth", GST_DEBUG_BOLD | GST_DEBUG_FG_YELLOW | GST_DEBUG_BG_BLUE, "PhotoBooth"); + GST_DEBUG ("photo_booth_class_init"); + gobject_class->finalize = photo_booth_finalize; +} + +static void photo_booth_init (PhotoBooth *pb) +{ + GST_DEBUG_OBJECT (pb, "photo_booth_init init object!"); + + int control_sock[2]; + if (socketpair (PF_UNIX, SOCK_STREAM, 0, control_sock) < 0) + { + GST_ERROR_OBJECT (pb, "cannot create control sockets: %s (%i)", strerror(errno), errno); + return; + } + READ_SOCKET (pb) = control_sock[0]; + WRITE_SOCKET (pb) = control_sock[1]; + fcntl (READ_SOCKET (pb), F_SETFL, O_NONBLOCK); + fcntl (WRITE_SOCKET (pb), F_SETFL, O_NONBLOCK); + + if (!_pb_cam_init (&pb->cam_info)) + g_error ("can't init cam!"); + + pb->video_capture_thread = NULL; + pb->video_capture_thread = g_thread_try_new ("video-capture", (GThreadFunc) _pb_video_capture_thread_func, pb, NULL); +} + +static void photo_booth_finalize (GObject * object) +{ + PhotoBooth *pb = PHOTO_BOOTH (object); + GST_INFO_OBJECT (pb, "finalize server"); + SEND_COMMAND (pb, CONTROL_STOP); + _pb_flush_pipe (pb->video_fd); + g_thread_join (pb->video_capture_thread); + _pb_cam_close (&pb->cam_info); +} +static void _gphoto_gst_errordumper(GPLogLevel level, const char *domain, const char *str, void *data) +{ + GST_DEBUG ("GPhoto %d, %s:%s", (int) level, domain, str); +} + +gboolean _pb_cam_init (CameraInfo *cam_info) +{ + int retval; + + cam_info->context = gp_context_new(); + gp_log_add_func(GP_LOG_ERROR, _gphoto_gst_errordumper, NULL); + gp_camera_new(&cam_info->camera); + retval = gp_camera_init(cam_info->camera, cam_info->context); + GST_DEBUG ("gp_camera_init returned %d", retval); + if (retval != GP_OK) { + return FALSE; + } + return TRUE; +} + +gboolean _pb_cam_close (CameraInfo *cam_info) +{ + int retval; + retval = gp_camera_exit(cam_info->camera, cam_info->context); + GST_DEBUG ("gp_camera_exit returned %i", retval); + return GP_OK ? TRUE : FALSE; +} + +void _pb_flush_pipe (int fd) +{ + int rlen = 0; + unsigned char buf[1024]; + fcntl (fd, F_SETFL, O_NONBLOCK); + while (rlen != -1) + rlen = read (fd, buf, sizeof(buf)); +} +void _pb_quit_signal (GMainLoop *loop) +{ + GST_INFO_OBJECT (loop, "caught SIGINT"); + g_main_loop_quit (loop); +} + +static void _pb_video_capture_thread_func (PhotoBooth *pb) +{ + PhotoboothReadthreadState state = CAPTURETHREAD_NONE; + + mkfifo("moviepipe", 0666); + pb->video_fd = open("moviepipe", O_RDWR); + + GST_DEBUG_OBJECT (pb, "enter video capture thread fd = %d", pb->video_fd); + + CameraFile *gp_file = NULL; + int gp_r, captured_frames = 0; + + if (gp_file_new_from_fd (&gp_file, pb->video_fd) != GP_OK) + { + GST_ERROR_OBJECT (pb, "couldn't start video capture thread because gp_file_new_from_fd (%d) failed!", pb->video_fd); + goto stop_running; + } + + while (TRUE) { + if (state == CAPTURETHREAD_STOP) + goto stop_running; + + struct pollfd rfd[2]; + int timeout = 1000 / PREVIEW_FPS; + rfd[0].fd = READ_SOCKET (pb); + rfd[0].events = POLLIN | POLLERR | POLLHUP | POLLPRI; + + int ret = poll(rfd, 1, timeout); + if (G_UNLIKELY (ret == -1)) + { + GST_ERROR_OBJECT (pb, "SELECT ERROR!"); + goto stop_running; + } + else if ( ret == 0 ) + { + const char *mime; + gp_r = gp_camera_capture_preview (pb->cam_info.camera, gp_file, pb->cam_info.context); + if (gp_r < 0) { + GST_ERROR_OBJECT (pb, "Movie capture error."); + state = CAPTURETHREAD_STOP; + break; + } + gp_file_get_mime_type (gp_file, &mime); + if (strcmp (mime, GP_MIME_JPEG)) { + g_print ("Movie capture error... Unhandled MIME type '%s'.", mime); + state = CAPTURETHREAD_STOP; + break; + } + captured_frames++; + } + else if ( rfd[0].revents ) + { + char command; + READ_COMMAND (pb, command, ret); + switch (command) { + case CONTROL_STOP: + GST_DEBUG_OBJECT (pb, "CONTROL_STOP!"); + state = CAPTURETHREAD_STOP; + break; + case CONTROL_RUN: + GST_DEBUG_OBJECT (pb, "CONTROL_RUN"); + state = CAPTURETHREAD_RUN; + break; + default: + GST_ERROR_OBJECT (pb, "illegal control socket command %c received!", command); + } + continue; + } + } + + g_assert_not_reached (); + return; + + stop_running: + { + if (gp_file) + gp_file_unref (gp_file); + if (pb->video_fd) + close (pb->video_fd); + GST_DEBUG ("stop running, exit thread, %d frames captured", captured_frames); + return; + } + +} + +int main (int argc, char *argv[]) +{ + GMainLoop *loop; + PhotoBooth *pb; + + gst_init (&argc, &argv); + gtk_init (0, NULL); + + loop = g_main_loop_new (NULL, FALSE); + + g_unix_signal_add (SIGINT, (GSourceFunc) _pb_quit_signal, loop); + + pb = g_object_new (PHOTO_BOOTH_TYPE, NULL); + + g_main_loop_run (loop); + + g_object_unref (pb); + return 0; +} + diff --git a/photobooth.h b/photobooth.h new file mode 100644 index 0000000..baa0bd0 --- /dev/null +++ b/photobooth.h @@ -0,0 +1,103 @@ +/* + * GStreamer photobooth.c + * Copyright 2016 Andreas Frisch + * + * This program is licensed under the Creative Commons + * Attribution-NonCommercial-ShareAlike 3.0 Unported + * License. To view a copy of this license, visit + * http://creativecommons.org/licenses/by-nc-sa/3.0/ or send a letter to + * Creative Commons,559 Nathan Abbott Way,Stanford,California 94305,USA. + * + * This program is NOT free software. It is open source, you are allowed + * to modify it (if you keep the license), but it may not be commercially + * distributed other than under the conditions noted above. + */ + +#ifndef __PHOTO_BOOTH_H__ +#define __PHOTO_BOOTH_H__ + + +#include +#include +#include +#include + +#define CONTROL_RUN 'R' /* start movie capture */ +#define CONTROL_STOP 'S' /* stop movie capture */ +#define CONTROL_SOCKETS(src) src->control_sock +#define WRITE_SOCKET(src) src->control_sock[1] +#define READ_SOCKET(src) src->control_sock[0] + +#define CLEAR_COMMAND(src) \ +G_STMT_START { \ + char c; \ + read(READ_SOCKET(src), &c, 1); \ +} G_STMT_END + +#define SEND_COMMAND(src, command) \ +G_STMT_START { \ + int G_GNUC_UNUSED _res; unsigned char c; c = command; \ + _res = write (WRITE_SOCKET(src), &c, 1); \ +} G_STMT_END + +#define READ_COMMAND(src, command, res) \ +G_STMT_START { \ + res = read(READ_SOCKET(src), &command, 1); \ +} G_STMT_END + +G_BEGIN_DECLS + +#define PREVIEW_FPS 25 + +struct _CameraInfo { + Camera *camera; + GPContext *context; +}; + +typedef enum +{ + CAPTURETHREAD_NONE = 0, + CAPTURETHREAD_RUN, + CAPTURETHREAD_STOP +} PhotoboothReadthreadState; + +#define PHOTO_BOOTH_TYPE (photo_booth_get_type ()) +#define PHOTO_BOOTH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),PHOTO_BOOTH_TYPE,PhotoBooth)) +#define PHOTO_BOOTH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PHOTO_BOOTH_TYPE,PhotoBoothClass)) +#define IS_PHOTO_BOOTH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj),PHOTO_BOOTH_TYPE)) +#define IS_PHOTO_BOOTH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PHOTO_BOOTH_TYPE)) + +typedef struct _PhotoBooth PhotoBooth; +typedef struct _PhotoBoothClass PhotoBoothClass; + +typedef struct _CameraInfo CameraInfo; + +struct _PhotoBooth +{ + GMainLoop *loop; + GstElement *pipeline; + GstElement *mjpeg_source, *mjpeg_decoder, *mjpeg_filter, *video_filter, *video_scale, *video_convert; + GstElement *photo_source, *photo_decoder, *photo_freeze, *photo_scale, *photo_filter; + GstElement *pixoverlay, *video_sink; + int video_fd; + gint timeout_id; + GPid pid; + GtkWidget *window, *overlay; + GtkWidget *drawing_area, *text; + GdkRectangle monitor_geo; + CameraInfo cam_info; + + int control_sock[2]; + GThread *video_capture_thread; +}; + +struct _PhotoBoothClass +{ + GObjectClass parent_class; +}; + +GType photo_booth_get_type (void); + +G_END_DECLS + +#endif /* __PHOTO_BOOTH_H__ */ \ No newline at end of file