/* Handle various savings of a jpeg associated with a video recording. | For motion records: save a copy of the mjpeg.jpg for later processing | with on_motion_preview_save_cmd. If mode is "best", this copy may be | written multiple times. This copy will be disposed of. */ void event_preview_save(void) { FILE *f_src, *f_dst; char *s, *base, *path, buf[BUFSIZ]; int n; path = strdup(pikrellcam.video_pathname); if ( (s = strstr(path, ".mp4")) != NULL || (s = strstr(path, ".h264")) != NULL ) { strcpy(s, ".jpg"); /* Copy the current mjpeg.jpg into a motion preview file. */ if ((f_src = fopen(pikrellcam.mjpeg_filename, "r")) != NULL) { base = fname_base(path); if (pikrellcam.preview_filename) free(pikrellcam.preview_filename); asprintf(&pikrellcam.preview_filename, "%s/%s", pikrellcam.tmpfs_dir, base); log_printf("event preview save: copy %s -> %s\n", pikrellcam.mjpeg_filename, pikrellcam.preview_filename); if ((f_dst = fopen(pikrellcam.preview_filename, "w")) != NULL) { while ((n = fread(buf, 1, sizeof(buf), f_src)) > 0) { if (fwrite(buf, 1, n, f_dst) != n) break; } fclose(f_dst); } fclose(f_src); } } free(path); /* Let mmalcam.c mjpeg_callback() continue renaming the mjpeg file. */ pikrellcam.mjpeg_rename_holdoff = FALSE; }
/* In pikrellcam, this callback receives resized I420 frames before | sending them on to a jpeg encoder component which generates the | mjpeg.jpg stream image. Here we send the frame data to the motion display | routine for possible drawing of region outlines, motion vectors | and/or various status text. Motion detection was done in the h264 | callback and a flag is set there so these two paths can be synchronized | so motion vectors can be drawn on the right frame. */ void I420_video_callback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) { CameraObject *obj = (CameraObject *) port->userdata; MMAL_BUFFER_HEADER_T *buffer_in; static struct timeval timer; int utime; static int encoder_busy_count; if ( buffer->length > 0 && motion_frame_event ) { motion_frame_event = FALSE; /* Do not send buffer to encoder if it has not received the previous | one we sent unless this is the frame we want for a preview save. | In that case, we may be sending a buffer to preview save before | the previous buffer is handled. This is accounted for below. */ if ( mjpeg_encoder_send_count == mjpeg_encoder_recv_count || motion_frame.do_preview_save ) { if (obj->callback_port_in && obj->callback_pool_in) { buffer_in = mmal_queue_get(obj->callback_pool_in->queue); if ( buffer_in && obj->callback_port_in->buffer_size >= buffer->length ) { mmal_buffer_header_mem_lock(buffer); memcpy(buffer_in->data, buffer->data, buffer->length); buffer_in->length = buffer->length; mmal_buffer_header_mem_unlock(buffer); display_draw(buffer_in->data); if (motion_frame.do_preview_save) { /* If mjpeg encoder has not received previous buffer, | then the buffer to save will be the second buffer | it gets from now. Otherwise it's the next buffer. */ pthread_mutex_lock(&mjpeg_encoder_count_lock); if (mjpeg_encoder_send_count == mjpeg_encoder_recv_count) mjpeg_do_preview_save = 1; else mjpeg_do_preview_save = 2; pthread_mutex_unlock(&mjpeg_encoder_count_lock); if (mjpeg_do_preview_save == 2 && pikrellcam.debug) printf("%s: encoder not clear -> preview save delayed\n", fname_base(pikrellcam.video_pathname)); } motion_frame.do_preview_save = FALSE; ++mjpeg_encoder_send_count; mmal_port_send_buffer(obj->callback_port_in, buffer_in); } } } else { ++encoder_busy_count; if (pikrellcam.debug) printf("encoder not clear (%d) -> skipping mjpeg frame.\n", encoder_busy_count); if (encoder_busy_count > 2) /* Frame maybe dropped ??, move on */ { if (pikrellcam.debug) printf(" Syncing recv/send counts.\n"); encoder_busy_count = 0; mjpeg_encoder_recv_count = mjpeg_encoder_send_count; } } if (buffer->flags & MMAL_BUFFER_HEADER_FLAG_FRAME_END) { if (pikrellcam.debug_fps && (utime = micro_elapsed_time(&timer)) > 0) printf("%s fps %d\n", obj->name, 1000000 / utime); } } return_buffer_to_port(port, buffer); }
/* If video_fps is too high and strains GPU, resized frames to this | callback may be dropped. Set debug_fps to 1 to check things... or | just watch the web mjpeg stream and see it slow down. */ void mjpeg_callback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) { CameraObject *data = (CameraObject *) port->userdata; static struct timeval timer; int n, utime; static FILE *file = NULL; static char *fname_part; boolean do_preview_save = FALSE; static char *tcp_buf; static int tcp_buf_offset; if (!fname_part) asprintf(&fname_part, "%s.part", pikrellcam.mjpeg_filename); if (!tcp_buf) tcp_buf = mjpeg_server_queue_get(); if (file && buffer->length > 0) { mmal_buffer_header_mem_lock(buffer); n = fwrite(buffer->data, 1, buffer->length, file); if (tcp_buf) { memcpy(tcp_buf + tcp_buf_offset, buffer->data, buffer->length); tcp_buf_offset += buffer->length; } mmal_buffer_header_mem_unlock(buffer); if (n != buffer->length) { log_printf("mjpeg_callback: %s file write error. %m\n", data->name); exit(1); } } if (buffer->flags & MMAL_BUFFER_HEADER_FLAG_FRAME_END) { if (tcp_buf) { mjpeg_server_queue_put(tcp_buf, tcp_buf_offset); tcp_buf = NULL; tcp_buf_offset = 0; } if (pikrellcam.debug_fps && (utime = micro_elapsed_time(&timer)) > 0) printf("%s fps %d\n", data->name, 1000000 / utime); if (file) { fclose(file); file = NULL; pthread_mutex_lock(&mjpeg_encoder_count_lock); ++mjpeg_encoder_recv_count; if (mjpeg_do_preview_save == 1) { mjpeg_do_preview_save = 0; do_preview_save = TRUE; } else if (mjpeg_do_preview_save > 1) --mjpeg_do_preview_save; pthread_mutex_unlock(&mjpeg_encoder_count_lock); /* When adding an event_preview_save, set a rename holdoff that | will be reset when the preview_save is done. Don't do | any renames until the holdoff reset to ensure the correct | mjpeg image is saved into the preview. Otherwise there is | a race condition. Could just directly run the preview save | function here, but we are inside a GPU callback and I don't | want to overload the time spent here. */ if (!pikrellcam.mjpeg_rename_holdoff) rename(fname_part, pikrellcam.mjpeg_filename); else if (pikrellcam.debug) printf("%s: holdoff not clear -> rename skipped\n", fname_base(pikrellcam.video_pathname)); if (do_preview_save) { pikrellcam.mjpeg_rename_holdoff = TRUE; event_add("motion preview save", pikrellcam.t_now, 0, event_preview_save, NULL); if (motion_frame.do_preview_save_cmd) { event_add("motion area thumb", pikrellcam.t_now, 0, event_motion_area_thumb, NULL); event_add("preview save command", pikrellcam.t_now, 0, event_preview_save_cmd, pikrellcam.on_motion_preview_save_cmd); } } motion_frame.do_preview_save_cmd = FALSE; } file = fopen(fname_part, "w"); if (!file) log_printf("mjpeg_callback: could not open %s file. %m", fname_part); } return_buffer_to_port(port, buffer); }