/* * 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; }