Browse Source

Add support for short movies

Short movies can now be recorded (~30 seconds, depending on disk
space), with audio. New button is added, for that, depending on state
it shows "Rec" (you can record), "Stop" (you are recording) and "Busy"
(movie is being converted).
Pavel Machek 1 year ago
parent
commit
1c9202c4cf
4 changed files with 200 additions and 31 deletions
  1. 6 0
      data/camera.ui
  2. 31 2
      src/main.c
  3. 159 28
      src/process_pipeline.c
  4. 4 1
      src/process_pipeline.h

+ 6 - 0
data/camera.ui

@@ -228,6 +228,12 @@
                         <property name="margin-top">5</property>
                         <property name="margin-top">5</property>
                         <property name="margin-bottom">5</property>
                         <property name="margin-bottom">5</property>
                         <property name="spacing">5</property>
                         <property name="spacing">5</property>
+			<child>
+			  <object class="GtkButton" id="movie">
+			    <property name="valign">start</property>
+			    <property name="label">Rec</property>
+			  </object>
+			</child>
                         <child>
                         <child>
                           <object class="GtkButton">
                           <object class="GtkButton">
                             <property name="action-name">app.open-last</property>
                             <property name="action-name">app.open-last</property>

+ 31 - 2
src/main.c

@@ -46,6 +46,8 @@ RENDERDOC_API_1_1_2 *rdoc_api = NULL;
 
 
 mp_state_main state;
 mp_state_main state;
 
 
+static int movie_start = 0;
+
 static MPProcessPipelineBuffer *current_preview_buffer = NULL;
 static MPProcessPipelineBuffer *current_preview_buffer = NULL;
 
 
 static char last_path[260] = "";
 static char last_path[260] = "";
@@ -71,6 +73,7 @@ GtkWidget *iso_button;
 GtkWidget *shutter_button;
 GtkWidget *shutter_button;
 
 
 LfbEvent *capture_event;
 LfbEvent *capture_event;
+static GtkWidget *movie;
 
 
 GSettings *settings;
 GSettings *settings;
 GSettings *fb_settings;
 GSettings *fb_settings;
@@ -904,6 +907,27 @@ flash_button_clicked(GtkWidget *button, gpointer user_data)
         gtk_button_set_icon_name(GTK_BUTTON(button), icon_name);
         gtk_button_set_icon_name(GTK_BUTTON(button), icon_name);
 }
 }
 
 
+void
+notify_movie_progress(void)
+{
+	if (!movie_start)
+		gtk_button_set_label(GTK_BUTTON(movie), "Rec");
+}
+
+void
+on_movie_clicked(GtkWidget *widget, gpointer user_data)
+{
+	if (movie_start) {
+		movie_start = 0;
+		gtk_button_set_label(GTK_BUTTON(movie), "Busy");
+		on_movie_stop();
+	} else {
+		movie_start = 1;
+		gtk_button_set_label(GTK_BUTTON(movie), "Stop");
+		on_movie_start();
+	}
+}
+
 static void
 static void
 on_realize(GtkWidget *window, gpointer *data)
 on_realize(GtkWidget *window, gpointer *data)
 {
 {
@@ -1204,6 +1228,7 @@ activate(GtkApplication *app, gpointer data)
         preview_top_box = GTK_WIDGET(gtk_builder_get_object(builder, "top-box"));
         preview_top_box = GTK_WIDGET(gtk_builder_get_object(builder, "top-box"));
         preview_bottom_box =
         preview_bottom_box =
                 GTK_WIDGET(gtk_builder_get_object(builder, "bottom-box"));
                 GTK_WIDGET(gtk_builder_get_object(builder, "bottom-box"));
+	movie = GTK_WIDGET(gtk_builder_get_object(builder, "movie"));
 
 
         message_box = GTK_WIDGET(gtk_builder_get_object(builder, "message-box"));
         message_box = GTK_WIDGET(gtk_builder_get_object(builder, "message-box"));
         message_label = GTK_WIDGET(gtk_builder_get_object(builder, "message-label"));
         message_label = GTK_WIDGET(gtk_builder_get_object(builder, "message-label"));
@@ -1223,7 +1248,10 @@ activate(GtkApplication *app, gpointer data)
         g_signal_connect(
         g_signal_connect(
                 flash_button, "clicked", G_CALLBACK(flash_button_clicked), NULL);
                 flash_button, "clicked", G_CALLBACK(flash_button_clicked), NULL);
 
 
-        setup_fb_switch(builder);
+	g_signal_connect(movie, "clicked",
+			 G_CALLBACK(on_movie_clicked), NULL);
+
+	setup_fb_switch(builder);
 
 
         // Setup actions
         // Setup actions
         create_simple_action(app, "capture", G_CALLBACK(run_capture_action));
         create_simple_action(app, "capture", G_CALLBACK(run_capture_action));
@@ -1254,7 +1282,7 @@ activate(GtkApplication *app, gpointer data)
         if (setting_postproc == NULL || setting_postproc[0] == '\0') {
         if (setting_postproc == NULL || setting_postproc[0] == '\0') {
                 printf("Initializing postprocessor gsetting\n");
                 printf("Initializing postprocessor gsetting\n");
                 setting_postproc = malloc(512);
                 setting_postproc = malloc(512);
-                if (!mp_process_find_processor(setting_postproc)) {
+                if (!mp_process_find_processor(setting_postproc, "postprocess.sh")) {
                         printf("No processor found\n");
                         printf("No processor found\n");
                         exit(1);
                         exit(1);
                 }
                 }
@@ -1391,6 +1419,7 @@ main(int argc, char *argv[])
                         return 1;
                         return 1;
                 }
                 }
         } else {
         } else {
+                printf("megapixels: No suitable config, defaulting to uvc\n");
                 if (!libmegapixels_load_uvc(state.configuration)) {
                 if (!libmegapixels_load_uvc(state.configuration)) {
                         fprintf(stderr, "No config found\n");
                         fprintf(stderr, "No config found\n");
                         return 1;
                         return 1;

+ 159 - 28
src/process_pipeline.c

@@ -12,6 +12,7 @@
 #ifndef SYSCONFDIR
 #ifndef SYSCONFDIR
 #include "config.h"
 #include "config.h"
 #endif
 #endif
+#include "medianame.h"
 
 
 #include "dcp.h"
 #include "dcp.h"
 #include "gl_util.h"
 #include "gl_util.h"
@@ -19,6 +20,7 @@
 #include <jpeglib.h>
 #include <jpeglib.h>
 #include <sys/mman.h>
 #include <sys/mman.h>
 #include <sys/prctl.h>
 #include <sys/prctl.h>
+#include <sys/time.h>
 
 
 static const float colormatrix_srgb[] = { 3.2409f,  -1.5373f, -0.4986f,
 static const float colormatrix_srgb[] = { 3.2409f,  -1.5373f, -0.4986f,
                                           -0.9692f, 1.8759f,  0.0415f,
                                           -0.9692f, 1.8759f,  0.0415f,
@@ -27,7 +29,7 @@ static const float colormatrix_srgb[] = { 3.2409f,  -1.5373f, -0.4986f,
 static MPPipeline *pipeline;
 static MPPipeline *pipeline;
 mp_state_proc state_proc;
 mp_state_proc state_proc;
 
 
-static char burst_dir[23];
+static char burst_dir[255];
 
 
 static volatile bool is_capturing = false;
 static volatile bool is_capturing = false;
 static volatile int frames_processed = 0;
 static volatile int frames_processed = 0;
@@ -41,7 +43,7 @@ static int output_buffer_height = -1;
 static bool flash_enabled;
 static bool flash_enabled;
 static int framecounter = 0;
 static int framecounter = 0;
 
 
-static char capture_fname[255];
+static char capture_fname[255], movie_script[255];
 
 
 static GSettings *settings;
 static GSettings *settings;
 
 
@@ -113,10 +115,8 @@ mp_process_find_all_processors(GtkListStore *store)
 }
 }
 
 
 bool
 bool
-mp_process_find_processor(char *script)
+mp_process_find_processor(char *script, char *filename)
 {
 {
-        char filename[] = "postprocess.sh";
-
         // Check postprocess.sh in the current working directory
         // Check postprocess.sh in the current working directory
         sprintf(script, "./data/%s", filename);
         sprintf(script, "./data/%s", filename);
         if (access(script, F_OK) != -1) {
         if (access(script, F_OK) != -1) {
@@ -149,6 +149,20 @@ mp_process_find_processor(char *script)
         return false;
         return false;
 }
 }
 
 
+static void setup_capture(void)
+{
+	char template[] = "/tmp/megapixels.XXXXXX";
+	char *tempdir;
+	tempdir = mkdtemp(template);
+
+	if (tempdir == NULL) {
+		g_printerr("Could not make capture directory %s\n", template);
+		exit(EXIT_FAILURE);
+	}
+
+	strcpy(burst_dir, tempdir);
+}
+
 static void
 static void
 setup(MPPipeline *pipeline, const void *data)
 setup(MPPipeline *pipeline, const void *data)
 {
 {
@@ -159,6 +173,12 @@ setup(MPPipeline *pipeline, const void *data)
         state_proc.mode_balance = AAA_BY_POST;
         state_proc.mode_balance = AAA_BY_POST;
         state_proc.mode_exposure = AAA_BY_V4L2_CONTROLS;
         state_proc.mode_exposure = AAA_BY_V4L2_CONTROLS;
         state_proc.mode_focus = AAA_DISABLED;
         state_proc.mode_focus = AAA_DISABLED;
+
+	if (!mp_process_find_processor(movie_script, "movie.sh")) {
+		printf("movie.sh not found\n");
+		exit(1);
+	}
+	setup_capture();
 }
 }
 
 
 void
 void
@@ -582,10 +602,94 @@ process_image_for_preview(const uint8_t *image)
 }
 }
 
 
 static void
 static void
-process_image_for_capture_yuv(const uint8_t *image, int count)
+format_timestamp(char *timestamp)
+{
+	static char capture_fname[255];
+        time_t rawtime;
+        time(&rawtime);
+        struct tm tim = *(localtime(&rawtime));
+
+        strftime(timestamp, 30, "%Y%m%d%H%M%S", &tim);
+}
+
+static void
+format_movie_name(char *capture_fname)
+{
+	char timestamp[30];
+	format_timestamp(timestamp);
+
+	if (g_get_user_special_dir(G_USER_DIRECTORY_VIDEOS) != NULL) {
+		sprintf(capture_fname,
+			"%s/VID%s.mp4",
+			g_get_user_special_dir(G_USER_DIRECTORY_VIDEOS),
+			timestamp);
+	} else if (getenv("XDG_VIDOES_DIR") != NULL) {
+		sprintf(capture_fname,
+			"%s/VID%s.mp4",
+			getenv("XDG_VIDEOS_DIR"),
+			timestamp);
+	} else {
+		sprintf(capture_fname,
+			"%s/Videos/VID%s.mp4",
+			getenv("HOME"),
+			timestamp);
+	}
+}
+
+int movie_recording;
+static char movie_fname[255];
+
+static void
+on_movie_finished(GSubprocess *proc, GAsyncResult *res, GdkTexture *thumb)
+{
+	notify_movie_progress();
+}
+
+static void
+spawn_movie(char *cmd)
+{
+        g_autoptr(GError) error = NULL;
+        GSubprocess *proc = g_subprocess_new(0,
+                                             &error,
+                                             movie_script,
+					     cmd,
+                                             burst_dir,
+                                             movie_fname,
+					     "305",
+                                             NULL);
+
+        if (!proc) {
+                g_printerr("Failed to spawn postprocess process: %s\n",
+                           error->message);
+                return;
+        }
+
+        g_subprocess_communicate_utf8_async(
+                proc, NULL, NULL, (GAsyncReadyCallback)on_movie_finished, NULL);
+}
+
+
+void
+on_movie_start(void)
+{
+	format_movie_name(movie_fname);
+
+	movie_recording = 1;
+	printf("movie recording on\n");
+	spawn_movie("start");
+}
+
+void
+on_movie_stop(void)
+{
+	movie_recording = 0;
+	printf("movie recording off\n");
+	spawn_movie("stop");
+}
+
+static void
+save_jpeg(const uint8_t *image, char *fname)
 {
 {
-        char fname[255];
-        sprintf(fname, "%s/%d.jpg", burst_dir, count);
         FILE *outfile;
         FILE *outfile;
         if ((outfile = fopen(fname, "wb")) == NULL) {
         if ((outfile = fopen(fname, "wb")) == NULL) {
                 g_printerr("jpeg open %s: error %d, %s\n",
                 g_printerr("jpeg open %s: error %d, %s\n",
@@ -603,8 +707,9 @@ process_image_for_capture_yuv(const uint8_t *image, int count)
         jpeg_create_compress(&cinfo);
         jpeg_create_compress(&cinfo);
         jpeg_stdio_dest(&cinfo, outfile);
         jpeg_stdio_dest(&cinfo, outfile);
 
 
+	//printf("Saving jpeg, %d x %d\n", cinfo.image_width, cinfo.image_height);
         cinfo.image_width = state_proc.mode->width & -1;
         cinfo.image_width = state_proc.mode->width & -1;
-        cinfo.image_height = state_proc.mode->height & -1;
+        cinfo.image_height = state_proc.mode->height & -1; // FIXME?
         cinfo.input_components = 3;
         cinfo.input_components = 3;
         cinfo.in_color_space = JCS_YCbCr;
         cinfo.in_color_space = JCS_YCbCr;
         jpeg_set_defaults(&cinfo);
         jpeg_set_defaults(&cinfo);
@@ -667,11 +772,17 @@ process_image_for_capture_yuv(const uint8_t *image, int count)
 }
 }
 
 
 static void
 static void
-process_image_for_capture_bayer(const uint8_t *image, int count)
+process_image_for_capture_yuv(const uint8_t *image, int count)
 {
 {
-        char fname[255];
-        sprintf(fname, "%s/%d.dng", burst_dir, count);
+	char fname[255];
+	sprintf(fname, "%s/%d.jpeg", burst_dir, count);
+
+	save_jpeg(image, fname);
+}
 
 
+static void
+save_dng(const uint8_t *image, char *fname, int count)
+{
         uint16_t orientation;
         uint16_t orientation;
         if (state_proc.device_rotation == 0) {
         if (state_proc.device_rotation == 0) {
                 orientation = state_proc.mode->mirrored ?
                 orientation = state_proc.mode->mirrored ?
@@ -716,7 +827,7 @@ process_image_for_capture_bayer(const uint8_t *image, int count)
                 libdng_set_exposure_program(&dng, LIBDNG_EXPOSUREPROGRAM_MANUAL);
                 libdng_set_exposure_program(&dng, LIBDNG_EXPOSUREPROGRAM_MANUAL);
         }
         }
 
 
-        printf("Writing frame to %s\n", fname);
+        //printf("Writing frame to %s, %d x %d\n", fname, state_proc.mode->width, state_proc.mode->height);
         libdng_write(&dng,
         libdng_write(&dng,
                      fname,
                      fname,
                      state_proc.mode->width,
                      state_proc.mode->width,
@@ -766,6 +877,15 @@ process_image_for_capture_bayer(const uint8_t *image, int count)
          */
          */
 }
 }
 
 
+static void
+process_image_for_capture_bayer(const uint8_t *image, int count)
+{
+	char fname[255];
+	sprintf(fname, "%s/%d.dng", burst_dir, count);
+
+	save_dng(image, fname, count);
+}
+
 static void
 static void
 process_image_for_capture(const uint8_t *image, int count)
 process_image_for_capture(const uint8_t *image, int count)
 {
 {
@@ -808,12 +928,10 @@ post_process_finished(GSubprocess *proc, GAsyncResult *res, GdkTexture *thumb)
 static void
 static void
 process_capture_burst(GdkTexture *thumb)
 process_capture_burst(GdkTexture *thumb)
 {
 {
-        time_t rawtime;
-        time(&rawtime);
-        struct tm tim = *(localtime(&rawtime));
+	static char capture_fname[255];
 
 
-        char timestamp[30];
-        strftime(timestamp, 30, "%Y%m%d%H%M%S", &tim);
+	char timestamp[30];
+	format_timestamp(timestamp);
 
 
         if (g_get_user_special_dir(G_USER_DIRECTORY_PICTURES) != NULL) {
         if (g_get_user_special_dir(G_USER_DIRECTORY_PICTURES) != NULL) {
                 sprintf(capture_fname,
                 sprintf(capture_fname,
@@ -885,6 +1003,12 @@ process_image(MPPipeline *pipeline, const MPBuffer *buffer)
         memcpy(image, buffer->data, size);
         memcpy(image, buffer->data, size);
         mp_io_pipeline_release_buffer(buffer->index);
         mp_io_pipeline_release_buffer(buffer->index);
 
 
+	if (movie_recording) {
+		char name[1024];
+		get_name(name, burst_dir, "dng");
+		save_dng(image, name, 1);
+	}
+
         MPZBarImage *zbar_image = mp_zbar_image_new(image,
         MPZBarImage *zbar_image = mp_zbar_image_new(image,
                                                     state_proc.mode->format,
                                                     state_proc.mode->format,
                                                     state_proc.mode->width,
                                                     state_proc.mode->width,
@@ -933,8 +1057,24 @@ process_image(MPPipeline *pipeline, const MPBuffer *buffer)
 void
 void
 mp_process_pipeline_process_image(MPBuffer buffer)
 mp_process_pipeline_process_image(MPBuffer buffer)
 {
 {
+#ifdef DEBUG_FPS
+	static clock_t last, now;
+	static int last_n, now_n;
+	now_n++;
+	now = clock();
+	if (now - last > CLOCKS_PER_SEC * 10) {
+		printf("period %fms -- %d -- %f fps\n",
+		       (float)(now  - last) / CLOCKS_PER_SEC * 1000,
+		       now_n - last_n,
+		       ((float) now_n - last_n) / ((now  - last) / CLOCKS_PER_SEC));
+		last = now;
+		last_n = now_n;
+	}
+#endif
+
         // If we haven't processed the previous frame yet, drop this one
         // If we haven't processed the previous frame yet, drop this one
         if (frames_received != frames_processed && !is_capturing) {
         if (frames_received != frames_processed && !is_capturing) {
+		printf("Dropping frame\n");
                 mp_io_pipeline_release_buffer(buffer.index);
                 mp_io_pipeline_release_buffer(buffer.index);
                 return;
                 return;
         }
         }
@@ -950,16 +1090,7 @@ mp_process_pipeline_process_image(MPBuffer buffer)
 static void
 static void
 capture()
 capture()
 {
 {
-        char template[] = "/tmp/megapixels.XXXXXX";
-        char *tempdir;
-        tempdir = mkdtemp(template);
-
-        if (tempdir == NULL) {
-                g_printerr("Could not make capture directory %s\n", template);
-                exit(EXIT_FAILURE);
-        }
-
-        strcpy(burst_dir, tempdir);
+	setup_capture();
 
 
         state_proc.captures_remaining = state_proc.burst_length;
         state_proc.captures_remaining = state_proc.burst_length;
         state_proc.counter = 0;
         state_proc.counter = 0;

+ 4 - 1
src/process_pipeline.h

@@ -38,7 +38,7 @@ struct mp_process_pipeline_state {
         bool control_focus;
         bool control_focus;
 };
 };
 
 
-bool mp_process_find_processor(char *script);
+bool mp_process_find_processor(char *script, char *filename);
 void mp_process_find_all_processors(GtkListStore *store);
 void mp_process_find_all_processors(GtkListStore *store);
 
 
 void mp_process_pipeline_start();
 void mp_process_pipeline_start();
@@ -51,6 +51,9 @@ void mp_process_pipeline_process_image(MPBuffer buffer);
 void mp_process_pipeline_capture();
 void mp_process_pipeline_capture();
 void mp_process_pipeline_update_state(const mp_state_proc *new_state);
 void mp_process_pipeline_update_state(const mp_state_proc *new_state);
 
 
+void on_movie_start(void);
+void on_movie_stop(void);
+
 typedef struct _MPProcessPipelineBuffer MPProcessPipelineBuffer;
 typedef struct _MPProcessPipelineBuffer MPProcessPipelineBuffer;
 
 
 void mp_process_pipeline_buffer_ref(MPProcessPipelineBuffer *buf);
 void mp_process_pipeline_buffer_ref(MPProcessPipelineBuffer *buf);