void camera_restart(void) { VideoCircularBuffer *vcb = &video_circular_buffer; pthread_mutex_lock(&vcb->mutex); video_record_stop(vcb); vcb->state = VCB_STATE_RESTARTING; pikrellcam.camera_adjust = camera_adjust_temp; /* May not be changed */ pthread_mutex_unlock(&vcb->mutex); camera_stop(); camera_start(); }
void command_process(char *command_line) { VideoCircularBuffer *vcb = &video_circular_buffer; Command *cmd; char command[64], args[128], buf[32], *path; int i, n; if (!command_line || *command_line == '\0') return; n = sscanf(command_line, "%63s %[^\n]", command, args); if (n < 1 || command[0] == '#') return; for (cmd = NULL, i = 0; i < COMMAND_SIZE; cmd = NULL, ++i) { cmd = &commands[i]; if (!strcmp(command, cmd->name)) { if (cmd->n_args != n - 1) { log_printf("Wrong number of args for command: %s\n", command); return; } break; } } if (!cmd) { if ( !config_set_option(command, args, FALSE) && !mmalcam_config_parameter_set(command, args, TRUE) ) log_printf("Bad command: [%s] [%s]\n", command, args); return; } if (cmd->code != display_cmd) log_printf("command_process: %s\n", command_line); if (cmd->code < display_cmd && !display_is_default()) { display_set_default(); return; } switch (cmd->code) { case record: pthread_mutex_lock(&vcb->mutex); if (config_boolean_value(args) == TRUE) { if (vcb->pause) vcb->pause = FALSE; else { if (vcb->state == VCB_STATE_MOTION_RECORD) video_record_stop(vcb); video_record_start(vcb, VCB_STATE_MANUAL_RECORD_START); } } else video_record_stop(vcb); pthread_mutex_unlock(&vcb->mutex); break; case record_pause: /* Can pause manual record only. Because of event_gap/capture | times, I'm not even sure what it would mean to pause a | motion record. */ pthread_mutex_lock(&vcb->mutex); if (vcb->state == VCB_STATE_MANUAL_RECORD) vcb->pause = vcb->pause ? FALSE : TRUE; else vcb->pause = FALSE; pthread_mutex_unlock(&vcb->mutex); break; case still: snprintf(buf, sizeof(buf), "%d", pikrellcam.still_sequence); path = media_pathname(pikrellcam.still_dir, pikrellcam.still_filename, 'N', buf, '\0', NULL); pikrellcam.still_sequence += 1; still_capture(path); free(path); break; case tl_start: if ((n = atoi(args)) < 1) n = 0; time_lapse.activated = TRUE; time_lapse.on_hold = FALSE; if (!time_lapse.event && n > 0) { time_lapse.sequence = 0; ++time_lapse.series; time_lapse.event = event_add("timelapse", pikrellcam.t_now, n, timelapse_capture, NULL); } else if (n > 0) /* Change the period */ { time_lapse.event->time += (n - time_lapse.period); time_lapse.event->period = n; } if (n > 0) time_lapse.period = n; /* n == 0 just sets on_hold FALSE */ config_timelapse_save_status(); break; case tl_hold: config_set_boolean(&time_lapse.on_hold, args); config_timelapse_save_status(); break; case tl_end: if (time_lapse.activated) { event_remove(time_lapse.event); time_lapse.event = NULL; time_lapse.activated = FALSE; time_lapse.on_hold = FALSE; config_timelapse_save_status(); exec_no_wait(pikrellcam.on_timelapse_end_cmd, NULL); } break; case tl_inform_convert: if (!strcmp(args, "done")) { event_remove(time_lapse.inform_event); dup_string(&time_lapse.convert_name, ""); time_lapse.convert_size = 0; } else { dup_string(&time_lapse.convert_name, args); time_lapse.inform_event = event_add("tl_inform_convert", pikrellcam.t_now, 5, timelapse_inform_convert, NULL); } break; case tl_show_status: config_set_boolean(&time_lapse.show_status, args); break; case display_cmd: display_command(args); break; case motion_cmd: motion_command(args); break; case motion_enable: n = motion_frame.motion_enable; config_set_boolean(&motion_frame.motion_enable, args); if (n && !motion_frame.motion_enable) { pthread_mutex_lock(&vcb->mutex); if (vcb->state == VCB_STATE_MOTION_RECORD) video_record_stop(vcb); pthread_mutex_unlock(&vcb->mutex); } break; case save_config: config_save(pikrellcam.config_file); break; case quit: config_timelapse_save_status(); if (pikrellcam.config_modified) config_save(pikrellcam.config_file); display_quit(); exit(0); break; default: log_printf("command in table with no action!\n"); break; } }
void video_h264_encoder_callback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *mmalbuf) { VideoCircularBuffer *vcb = &video_circular_buffer; MotionFrame *mf = &motion_frame; KeyFrame *kf; int i, end_space, t_elapsed, event = 0; int t_usec, dt_frame; boolean force_stop; time_t t_cur = pikrellcam.t_now; static int fps_count, pause_frame_count_adjust; static time_t t_sec, t_prev; uint64_t t64_now; static int t_annotate, t_key_frame; static struct timeval tv; static uint64_t t0_stc, pts_prev; static boolean prev_pause; if (vcb->state == VCB_STATE_RESTARTING) { if (mmalbuf->flags & MMAL_BUFFER_HEADER_FLAG_CONFIG) h264_header_save(mmalbuf); fps_count = 0; return_buffer_to_port(port, mmalbuf); return; } if (mmalbuf->pts > 0) { if (pikrellcam.t_now > tv.tv_sec + 10) { /* Rarely, but time skew can change if ntp updates. */ gettimeofday(&tv, NULL); mmal_port_parameter_get_uint64(port, MMAL_PARAMETER_SYSTEM_TIME, &t0_stc); t0_stc = (uint64_t) tv.tv_sec * 1000000LL + (uint64_t) tv.tv_usec - t0_stc; } /* Skew adjust to the system clock to get second transitions. | Annotate times need to be set early to get displayed time synced | with system time. Needs >2 frames + offset to get it to work over | range of fps values. | Key frames can be delivered one frame after a request, so request | within 1 1/2 frames before second time transitions. */ t64_now = t0_stc + mmalbuf->pts; t_sec = (int) (t64_now / 1000000LL); t_usec = (int) (t64_now % 1000000LL); dt_frame = 1000000 / pikrellcam.camera_adjust.video_fps; if ( t_annotate < t_sec && t_usec > 900000 - 5 * dt_frame / 2 ) { t_annotate = t_sec; annotate_text_update(t_annotate + 1); } if ( (vcb->state == VCB_STATE_NONE || vcb->pause) && t_key_frame < t_sec && t_usec > 1000000 - 3 * dt_frame / 2 ) { t_key_frame = t_sec; mmal_port_parameter_set_boolean(port, MMAL_PARAMETER_VIDEO_REQUEST_I_FRAME, 1); } if ( (mmalbuf->flags & MMAL_BUFFER_HEADER_FLAG_KEYFRAME) && t_cur < t_sec ) t_cur = t_sec; } pthread_mutex_lock(&vcb->mutex); if (mmalbuf->flags & MMAL_BUFFER_HEADER_FLAG_CONFIG) h264_header_save(mmalbuf); else if (mmalbuf->flags & MMAL_BUFFER_HEADER_FLAG_CODECSIDEINFO) { if (++fps_count >= pikrellcam.mjpeg_divider) { motion_frame_event = TRUE; /* synchronize with i420 callback */ fps_count = 0; mmal_buffer_header_mem_lock(mmalbuf); memcpy(motion_frame.vectors, mmalbuf->data, motion_frame.vectors_size); mmal_buffer_header_mem_unlock(mmalbuf); motion_frame_process(vcb, &motion_frame); } } else { if ( (mmalbuf->flags & MMAL_BUFFER_HEADER_FLAG_KEYFRAME) && !vcb->in_keyframe ) { /* Keep key_frame[cur_frame_index] always pointing to the latest | keyframe (this one) in the video buffer. Then adjust the | key_frame[pre_frame_index] to point to a keyframe | in the video buffer that is pre_capture time behind. | If paused, always keep tail pointing to the latest keyframe. */ vcb->in_keyframe = TRUE; vcb->cur_frame_index = (vcb->cur_frame_index + 1) % KEYFRAME_SIZE; kf = &vcb->key_frame[vcb->cur_frame_index]; kf->position = vcb->head; kf->frame_count = 0; pause_frame_count_adjust = 0; if (vcb->pause && vcb->state == VCB_STATE_MANUAL_RECORD) { vcb->tail = vcb->head; pts_prev = mmalbuf->pts; pause_frame_count_adjust = 0; } kf->t_frame = t_cur; kf->frame_pts = mmalbuf->pts; while (t_cur - vcb->key_frame[vcb->pre_frame_index].t_frame > pikrellcam.motion_times.pre_capture) { vcb->pre_frame_index = (vcb->pre_frame_index + 1) % KEYFRAME_SIZE; if (vcb->pre_frame_index == vcb->cur_frame_index) break; } } if (mmalbuf->flags & MMAL_BUFFER_HEADER_FLAG_FRAME_END) { vcb->in_keyframe = FALSE; i = vcb->pre_frame_index; while (1) { vcb->key_frame[i].frame_count += 1; if (i++ == vcb->cur_frame_index) break; i %= KEYFRAME_SIZE; } if ( vcb->state == VCB_STATE_MOTION_RECORD || vcb->state == VCB_STATE_MANUAL_RECORD ) { if (!vcb->pause) vcb->frame_count += 1; else pause_frame_count_adjust += 1; } } if (t_cur > t_prev) { if (!vcb->pause) ++vcb->record_elapsed_time; t_prev = t_cur; } if (vcb->state == VCB_STATE_MOTION_RECORD_START) { /* Write mp4 header and set tail to beginning of pre_capture | video data, then write the entire pre_capture time data. | The keyframe data we collected above keeps a pointer to | video data close to the pre_capture time we want. */ fwrite(vcb->h264_header, 1, vcb->h264_header_position, vcb->file); pikrellcam.video_header_size = vcb->h264_header_position; pikrellcam.video_size = vcb->h264_header_position; vcb->tail = vcb->key_frame[vcb->record_start_frame_index].position; vcb_video_write(vcb); vcb->frame_count = vcb->key_frame[vcb->record_start_frame_index].frame_count; vcb->video_frame_count = vcb->frame_count; motion_event_write(vcb, mf); vcb->state = VCB_STATE_MOTION_RECORD; if (mf->external_trigger_time_limit > 0) { vcb->motion_sync_time = t_cur + mf->external_trigger_time_limit; vcb->max_record_time = mf->external_trigger_time_limit; } else { vcb->motion_sync_time = t_cur + pikrellcam.motion_times.post_capture; vcb->max_record_time = pikrellcam.motion_record_time_limit; } /* Schedule any motion begin command. */ event |= EVENT_MOTION_BEGIN; } if (vcb->state == VCB_STATE_MANUAL_RECORD_START) { /* Write mp4 header and set tail to most recent keyframe. | So manual records may have up to about a sec pre_capture. */ fwrite(vcb->h264_header, 1, vcb->h264_header_position, vcb->file); pikrellcam.video_header_size = vcb->h264_header_position; pikrellcam.video_size = vcb->h264_header_position; vcb->tail = vcb->key_frame[vcb->record_start_frame_index].position; vcb_video_write(vcb); vcb->frame_count = vcb->key_frame[vcb->record_start_frame_index].frame_count; pts_prev = 0; vcb->state = VCB_STATE_MANUAL_RECORD; event |= EVENT_PREVIEW_SAVE; } if (h264_conn_status == H264_TCP_SEND_HEADER) tcp_send_h264_header(vcb->h264_header, vcb->h264_header_position); /* Save video data into the circular buffer. */ mmal_buffer_header_mem_lock(mmalbuf); end_space = vcb->size - vcb->head; if (mmalbuf->length <= end_space) { memcpy(vcb->data + vcb->head, mmalbuf->data, mmalbuf->length); if(h264_conn_status == H264_TCP_SEND_DATA) tcp_send_h264_data("data 1",vcb->data + vcb->head, mmalbuf->length); } else { memcpy(vcb->data + vcb->head, mmalbuf->data, end_space); memcpy(vcb->data, mmalbuf->data + end_space, mmalbuf->length - end_space); if (h264_conn_status == H264_TCP_SEND_DATA) { tcp_send_h264_data("data 2",vcb->data + vcb->head, end_space); tcp_send_h264_data("data 3",vcb->data, mmalbuf->length - end_space); } } vcb->head = (vcb->head + mmalbuf->length) % vcb->size; mmal_buffer_header_mem_unlock(mmalbuf); /* And write video data to a video file according to record state. | Record time limit (if any) does not include pre capture times or | manual paused time which is accounted for in record_elapsed_time. */ force_stop = FALSE; if (vcb->max_record_time > 0) { t_elapsed = vcb->record_elapsed_time; if (vcb->state == VCB_STATE_MOTION_RECORD) t_elapsed -= (mf->external_trigger_pre_capture > 0) ? mf->external_trigger_pre_capture : pikrellcam.motion_times.pre_capture; else t_elapsed -= vcb->manual_pre_capture; if (t_elapsed >= vcb->max_record_time) force_stop = TRUE; } if (vcb->state == VCB_STATE_MANUAL_RECORD) { if (!vcb->pause) { if (mmalbuf->pts > 0) { if (pts_prev > 0) vcb->last_pts += mmalbuf->pts - pts_prev; else vcb->last_pts = mmalbuf->pts; pts_prev = mmalbuf->pts; } if (prev_pause) { vcb->frame_count += pause_frame_count_adjust; pause_frame_count_adjust = 0; } vcb->video_frame_count = vcb->frame_count; vcb_video_write(vcb); /* Continuously write video data */ } prev_pause = vcb->pause; if (force_stop) video_record_stop(vcb); } else if (vcb->state == VCB_STATE_MOTION_RECORD) { /* Always write until we reach motion_sync time (which is last | motion detect time + post_capture time), then hold during | event_gap time. Motion events during event_gap time will bump | motion_sync_time and event_gap expiration time higher thus | triggering more writes up to the new sync_time. | If there is not another motion event, event_gap time will be | reached and we stop recording with the post_capture time | already written. */ if (t_cur <= vcb->motion_sync_time) { if (mmalbuf->pts > 0) vcb->last_pts = mmalbuf->pts; vcb->video_frame_count = vcb->frame_count; vcb_video_write(vcb); } if ( force_stop || ( mf->external_trigger_time_limit == 0 && t_cur >= vcb->motion_last_detect_time + pikrellcam.motion_times.event_gap ) ) { /* For motion recording, preview_save_mode "first" has been | handled in motion_frame_process(). But if not "first", | there is a preview save waiting to be handled. */ video_record_stop(vcb); event |= EVENT_MOTION_END; if (strcmp(pikrellcam.motion_preview_save_mode, "first") != 0) event |= EVENT_MOTION_PREVIEW_SAVE_CMD; } } } pthread_mutex_unlock(&vcb->mutex); return_buffer_to_port(port, mmalbuf); /* This handles preview saves for manual records for possible future use. | preview_save_cmd does not apply for manual records. | All preview saves for motion records are scheduled in motion_frame_process(). | preview_save_cmd for save mode "best" is handled in video_record_stop(). */ if (event & EVENT_PREVIEW_SAVE) event_add("manual preview save", pikrellcam.t_now, 0, event_preview_save, NULL); if ( (event & EVENT_MOTION_BEGIN) && *pikrellcam.on_motion_begin_cmd != '\0' ) event_add("motion begin", pikrellcam.t_now, 0, exec_no_wait, pikrellcam.on_motion_begin_cmd); /* motion end event and preview dispose are handled in video_record_stop() */ }