/*Calculate absolute time(in microseconds) using mmal_clock framework * get time at same clock frequency at which timestamp is calculated */ uint64_t Private_Impl::gettime() { uint64_t time; status = mmal_port_parameter_get_uint64(camera_video_port, 13, &time); return time; }
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() */ }