diff --git a/data/camera.ui b/data/camera.ui index 9a280fd..85e7605 100644 --- a/data/camera.ui +++ b/data/camera.ui @@ -228,6 +228,12 @@ 5 5 5 + + + start + Rec + + app.open-last diff --git a/src/main.c b/src/main.c index d236e56..400a952 100644 --- a/src/main.c +++ b/src/main.c @@ -46,6 +46,8 @@ RENDERDOC_API_1_1_2 *rdoc_api = NULL; mp_state_main state; +static int movie_start = 0; + static MPProcessPipelineBuffer *current_preview_buffer = NULL; static char last_path[260] = ""; @@ -71,6 +73,7 @@ GtkWidget *iso_button; GtkWidget *shutter_button; LfbEvent *capture_event; +static GtkWidget *movie; GSettings *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); } +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 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_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_label = GTK_WIDGET(gtk_builder_get_object(builder, "message-label")); @@ -1223,7 +1248,10 @@ activate(GtkApplication *app, gpointer data) g_signal_connect( 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 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') { printf("Initializing postprocessor gsetting\n"); 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"); exit(1); } @@ -1391,6 +1419,7 @@ main(int argc, char *argv[]) return 1; } } else { + printf("megapixels: No suitable config, defaulting to uvc\n"); if (!libmegapixels_load_uvc(state.configuration)) { fprintf(stderr, "No config found\n"); return 1; diff --git a/src/process_pipeline.c b/src/process_pipeline.c index 153062a..50e84f4 100644 --- a/src/process_pipeline.c +++ b/src/process_pipeline.c @@ -12,6 +12,7 @@ #ifndef SYSCONFDIR #include "config.h" #endif +#include "medianame.h" #include "dcp.h" #include "gl_util.h" @@ -19,6 +20,7 @@ #include #include #include +#include static const float colormatrix_srgb[] = { 3.2409f, -1.5373f, -0.4986f, -0.9692f, 1.8759f, 0.0415f, @@ -27,7 +29,7 @@ static const float colormatrix_srgb[] = { 3.2409f, -1.5373f, -0.4986f, static MPPipeline *pipeline; mp_state_proc state_proc; -static char burst_dir[23]; +static char burst_dir[255]; static volatile bool is_capturing = false; static volatile int frames_processed = 0; @@ -41,7 +43,7 @@ static int output_buffer_height = -1; static bool flash_enabled; static int framecounter = 0; -static char capture_fname[255]; +static char capture_fname[255], movie_script[255]; static GSettings *settings; @@ -113,10 +115,8 @@ mp_process_find_all_processors(GtkListStore *store) } 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 sprintf(script, "./data/%s", filename); if (access(script, F_OK) != -1) { @@ -149,6 +149,20 @@ mp_process_find_processor(char *script) 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 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_exposure = AAA_BY_V4L2_CONTROLS; 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 @@ -582,10 +602,94 @@ process_image_for_preview(const uint8_t *image) } 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; if ((outfile = fopen(fname, "wb")) == NULL) { 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_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_height = state_proc.mode->height & -1; + cinfo.image_height = state_proc.mode->height & -1; // FIXME? cinfo.input_components = 3; cinfo.in_color_space = JCS_YCbCr; jpeg_set_defaults(&cinfo); @@ -667,11 +772,17 @@ process_image_for_capture_yuv(const uint8_t *image, int count) } 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; if (state_proc.device_rotation == 0) { 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); } - 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, fname, 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 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 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) { sprintf(capture_fname, @@ -885,6 +1003,12 @@ process_image(MPPipeline *pipeline, const MPBuffer *buffer) memcpy(image, buffer->data, size); 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, state_proc.mode->format, state_proc.mode->width, @@ -933,8 +1057,24 @@ process_image(MPPipeline *pipeline, const MPBuffer *buffer) void 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 (frames_received != frames_processed && !is_capturing) { + printf("Dropping frame\n"); mp_io_pipeline_release_buffer(buffer.index); return; } @@ -950,16 +1090,7 @@ mp_process_pipeline_process_image(MPBuffer buffer) static void 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.counter = 0; diff --git a/src/process_pipeline.h b/src/process_pipeline.h index 88811f4..957d4cb 100644 --- a/src/process_pipeline.h +++ b/src/process_pipeline.h @@ -38,7 +38,7 @@ struct mp_process_pipeline_state { 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_pipeline_start(); @@ -51,6 +51,9 @@ void mp_process_pipeline_process_image(MPBuffer buffer); void mp_process_pipeline_capture(); 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; void mp_process_pipeline_buffer_ref(MPProcessPipelineBuffer *buf);