/* * Timeshift thread */ void *timeshift_reader ( void *p ) { timeshift_t *ts = p; int nfds, end, run = 1, wait = -1, state; timeshift_seek_t *seek = &ts->seek; timeshift_file_t *tmp_file; int cur_speed = 100, keyframe_mode = 0; int64_t mono_now, mono_play_time = 0, mono_last_status = 0; int64_t deliver, deliver0, pause_time = 0, last_time = 0, skip_time = 0; int64_t i64; streaming_message_t *sm = NULL, *ctrl = NULL; streaming_skip_t *skip = NULL; tvhpoll_t *pd; tvhpoll_event_t ev = { 0 }; pd = tvhpoll_create(1); ev.fd = ts->rd_pipe.rd; ev.events = TVHPOLL_IN; tvhpoll_add(pd, &ev, 1); /* Output */ while (run) { // Note: Previously we allowed unlimited wait, but we now must wake periodically // to output status message if (wait < 0 || wait > 1000) wait = 1000; /* Wait for data */ if(wait) nfds = tvhpoll_wait(pd, &ev, 1, wait); else nfds = 0; wait = -1; end = 0; skip = NULL; mono_now = getfastmonoclock(); /* Control */ pthread_mutex_lock(&ts->state_mutex); if (nfds == 1) { if (_read_msg(NULL, ts->rd_pipe.rd, &ctrl) > 0) { /* Exit */ if (ctrl->sm_type == SMT_EXIT) { tvhtrace(LS_TIMESHIFT, "ts %d read exit request", ts->id); run = 0; streaming_msg_free(ctrl); ctrl = NULL; /* Speed */ } else if (ctrl->sm_type == SMT_SPEED) { int speed = ctrl->sm_code; int keyframe; /* Bound it */ if (speed > 3200) speed = 3200; if (speed < -3200) speed = -3200; /* Ignore negative */ if (!ts->dobuf && (speed < 0)) speed = seek->file ? speed : 0; /* Process */ if (cur_speed != speed) { /* Live playback */ state = ts->state; if (state == TS_LIVE) { /* Reject */ if (speed >= 100) { tvhdebug(LS_TIMESHIFT, "ts %d reject 1x+ in live mode", ts->id); speed = 100; /* Set position */ } else { tvhdebug(LS_TIMESHIFT, "ts %d enter timeshift mode", ts->id); ts->dobuf = 1; _seek_reset(seek); tmp_file = timeshift_filemgr_newest(ts); if (tmp_file != NULL) { i64 = tmp_file->last; timeshift_file_put(tmp_file); } else { i64 = ts->buf_time; } seek->file = timeshift_filemgr_get(ts, i64); if (seek->file != NULL) { seek->file->roff = seek->file->size; pause_time = seek->file->last; last_time = pause_time; } else { pause_time = i64; last_time = pause_time; } } } /* Check keyframe mode */ keyframe = (speed < 0) || (speed > 400); if (keyframe != keyframe_mode) { tvhdebug(LS_TIMESHIFT, "using keyframe mode? %s", keyframe ? "yes" : "no"); keyframe_mode = keyframe; if (keyframe) seek->frame = NULL; } /* Update */ if (speed != 100 || state != TS_LIVE) { ts->state = speed == 0 ? TS_PAUSE : TS_PLAY; tvhtrace(LS_TIMESHIFT, "reader - set %s", speed == 0 ? "TS_PAUSE" : "TS_PLAY"); } if ((ts->state == TS_PLAY && state != TS_PLAY) || (speed != cur_speed)) { mono_play_time = mono_now; tvhtrace(LS_TIMESHIFT, "update play time TS_LIVE - %"PRId64" play buffer from %"PRId64, mono_now, pause_time); if (speed != cur_speed) pause_time = last_time; } else if (ts->state == TS_PAUSE && state != TS_PAUSE) { pause_time = last_time; } cur_speed = speed; tvhdebug(LS_TIMESHIFT, "ts %d change speed %d", ts->id, speed); } /* Send on the message */ ctrl->sm_code = speed; streaming_target_deliver2(ts->output, ctrl); ctrl = NULL; /* Skip/Seek */ } else if (ctrl->sm_type == SMT_SKIP) { skip = ctrl->sm_data; switch (skip->type) { case SMT_SKIP_LIVE: if (ts->state != TS_LIVE) { /* Reset */ if (ts->full) { timeshift_filemgr_flush(ts, NULL); _seek_reset(seek); ts->full = 0; } /* Release */ if (sm) streaming_msg_free(sm); /* Find end */ skip_time = 0x7fffffffffffffffLL; // TODO: change this sometime! } break; case SMT_SKIP_ABS_TIME: /* -fallthrough */ case SMT_SKIP_REL_TIME: /* Convert */ skip_time = ts_rescale(skip->time, 1000000); tvhdebug(LS_TIMESHIFT, "ts %d skip %"PRId64" requested %"PRId64, ts->id, skip_time, skip->time); /* Live playback (stage1) */ if (ts->state == TS_LIVE) { _seek_reset(seek); tmp_file = timeshift_filemgr_newest(ts); if (tmp_file) { i64 = tmp_file->last; timeshift_file_put(tmp_file); } if (tmp_file && (seek->file = timeshift_filemgr_get(ts, i64)) != NULL) { seek->file->roff = seek->file->size; last_time = seek->file->last; } else { last_time = ts->buf_time; } } /* May have failed */ if (skip->type == SMT_SKIP_REL_TIME) skip_time += last_time; tvhdebug(LS_TIMESHIFT, "ts %d skip time %"PRId64, ts->id, skip_time); /* Live (stage2) */ if (ts->state == TS_LIVE) { if (skip_time >= ts->buf_time - TIMESHIFT_PLAY_BUF) { tvhdebug(LS_TIMESHIFT, "ts %d skip ignored, already live", ts->id); skip = NULL; } else { ts->state = TS_PLAY; ts->dobuf = 1; tvhtrace(LS_TIMESHIFT, "reader - set TS_PLAY"); } } /* OK */ if (skip) { /* seek */ seek->frame = NULL; end = _timeshift_do_skip(ts, skip_time, last_time, seek); if (seek->frame) { pause_time = seek->frame->time; tvhtrace(LS_TIMESHIFT, "ts %d skip - play buffer from %"PRId64" last_time %"PRId64, ts->id, pause_time, last_time); /* Adjust time */ if (mono_play_time != mono_now) tvhtrace(LS_TIMESHIFT, "ts %d update play time skip - %"PRId64, ts->id, mono_now); mono_play_time = mono_now; /* Clear existing packet */ if (sm) { streaming_msg_free(sm); sm = NULL; } } else { skip = NULL; } } break; default: tvherror(LS_TIMESHIFT, "ts %d invalid/unsupported skip type: %d", ts->id, skip->type); skip = NULL; break; } /* Error */ if (!skip) { ((streaming_skip_t*)ctrl->sm_data)->type = SMT_SKIP_ERROR; streaming_target_deliver2(ts->output, ctrl); ctrl = NULL; } /* Ignore */ } else { streaming_msg_free(ctrl); ctrl = NULL; } } } /* Done */ if (!run || !seek->file || ((ts->state != TS_PLAY && !skip))) { if (mono_now >= (mono_last_status + sec2mono(1))) { timeshift_status(ts, last_time); mono_last_status = mono_now; } pthread_mutex_unlock(&ts->state_mutex); continue; } /* Calculate delivery time */ deliver0 = (mono_now - mono_play_time) + TIMESHIFT_PLAY_BUF; deliver = (deliver0 * cur_speed) / 100; deliver = (deliver + pause_time); tvhtrace(LS_TIMESHIFT, "speed %d now %"PRId64" play_time %"PRId64" deliver %"PRId64" deliver0 %"PRId64, cur_speed, mono_now, mono_play_time, deliver, deliver0); /* Determine next packet */ if (!sm) { /* Rewind or Fast forward (i-frame only) */ if (skip || keyframe_mode) { int64_t req_time; /* Time */ if (!skip) req_time = last_time + ((cur_speed < 0) ? -1 : 1); else req_time = skip_time; end = _timeshift_do_skip(ts, req_time, last_time, seek); } /* Clear old message */ if (sm) { streaming_msg_free(sm); sm = NULL; } /* Find packet */ if (_timeshift_read(ts, seek, &sm, &wait) == -1) { pthread_mutex_unlock(&ts->state_mutex); break; } } /* Send skip response */ if (skip) { if (sm) { /* Status message */ skip->time = ts_rescale_inv(sm->sm_time, 1000000); skip->type = SMT_SKIP_ABS_TIME; tvhdebug(LS_TIMESHIFT, "ts %d skip to pts %"PRId64" ok", ts->id, sm->sm_time); /* Update timeshift status */ timeshift_fill_status(ts, &skip->timeshift, sm->sm_time); mono_last_status = mono_now; } else { /* Report error */ skip->type = SMT_SKIP_ERROR; skip = NULL; tvhdebug(LS_TIMESHIFT, "ts %d skip failed (%d)", ts->id, sm ? sm->sm_type : -1); } streaming_target_deliver2(ts->output, ctrl); } else { streaming_msg_free(ctrl); } ctrl = NULL; /* Deliver */ if (sm && (skip || (((cur_speed < 0) && (sm->sm_time >= deliver)) || ((cur_speed > 0) && (sm->sm_time <= deliver))))) { last_time = sm->sm_time; if (!skip && keyframe_mode) /* always send status on keyframe mode */ timeshift_status(ts, last_time); timeshift_packet_log("out", ts, sm); streaming_target_deliver2(ts->output, sm); sm = NULL; wait = 0; } else if (sm) { if (cur_speed > 0) wait = (sm->sm_time - deliver) / 1000; else wait = (deliver - sm->sm_time) / 1000; if (wait == 0) wait = 1; tvhtrace(LS_TIMESHIFT, "ts %d wait %d speed %d sm_time %"PRId64" deliver %"PRId64, ts->id, wait, cur_speed, sm->sm_time, deliver); } /* Periodic timeshift status */ if (mono_now >= (mono_last_status + sec2mono(1))) { timeshift_status(ts, last_time); mono_last_status = mono_now; } /* Terminate */ if (!seek->file || end != 0) { /* Back to live (unless buffer is full) */ if ((end == 1 && !ts->full) || !seek->file) { tvhdebug(LS_TIMESHIFT, "ts %d eob revert to live mode", ts->id); cur_speed = 100; ctrl = streaming_msg_create_code(SMT_SPEED, cur_speed); streaming_target_deliver2(ts->output, ctrl); ctrl = NULL; tvhtrace(LS_TIMESHIFT, "reader - set TS_LIVE"); /* Flush timeshift buffer to live */ if (_timeshift_flush_to_live(ts, seek, &wait) == -1) { pthread_mutex_unlock(&ts->state_mutex); break; } ts->state = TS_LIVE; /* Close file (if open) */ _read_close(seek); /* Pause */ } else { if (cur_speed <= 0) { cur_speed = 0; tvhtrace(LS_TIMESHIFT, "reader - set TS_PAUSE"); ts->state = TS_PAUSE; } else { cur_speed = 100; tvhtrace(LS_TIMESHIFT, "reader - set TS_PLAY"); if (ts->state != TS_PLAY) { ts->state = TS_PLAY; ts->dobuf = 1; if (mono_play_time != mono_now) tvhtrace(LS_TIMESHIFT, "update play time (pause) - %"PRId64, mono_now); mono_play_time = mono_now; } } tvhdebug(LS_TIMESHIFT, "ts %d sob speed %d last time %"PRId64, ts->id, cur_speed, last_time); pause_time = last_time; ctrl = streaming_msg_create_code(SMT_SPEED, cur_speed); streaming_target_deliver2(ts->output, ctrl); ctrl = NULL; } } pthread_mutex_unlock(&ts->state_mutex); } /* Cleanup */ tvhpoll_destroy(pd); _read_close(seek); if (sm) streaming_msg_free(sm); if (ctrl) streaming_msg_free(ctrl); tvhtrace(LS_TIMESHIFT, "ts %d exit reader thread", ts->id); return NULL; }
/* * Get current / new file */ timeshift_file_t *timeshift_filemgr_get ( timeshift_t *ts, int create ) { int fd; struct timespec tp; timeshift_file_t *tsf_tl, *tsf_hd, *tsf_tmp; timeshift_index_data_t *ti; char path[512]; time_t time; /* Return last file */ if (!create) return timeshift_filemgr_newest(ts); /* No space */ if (ts->full) return NULL; /* Store to file */ clock_gettime(CLOCK_MONOTONIC_COARSE, &tp); time = tp.tv_sec / TIMESHIFT_FILE_PERIOD; tsf_tl = TAILQ_LAST(&ts->files, timeshift_file_list); if (!tsf_tl || tsf_tl->time != time) { tsf_hd = TAILQ_FIRST(&ts->files); /* Close existing */ if (tsf_tl && tsf_tl->fd != -1) timeshift_filemgr_close(tsf_tl); /* Check period */ if (ts->max_time && tsf_hd && tsf_tl) { time_t d = (tsf_tl->time - tsf_hd->time) * TIMESHIFT_FILE_PERIOD; if (d > (ts->max_time+5)) { if (!tsf_hd->refcount) { timeshift_filemgr_remove(ts, tsf_hd, 0); tsf_hd = NULL; } else { tvhlog(LOG_DEBUG, "timeshift", "ts %d buffer full", ts->id); ts->full = 1; } } } /* Check size */ if (!timeshift_unlimited_size && atomic_pre_add_u64(×hift_total_size, 0) >= timeshift_max_size) { /* Remove the last file (if we can) */ if (tsf_hd && !tsf_hd->refcount) { timeshift_filemgr_remove(ts, tsf_hd, 0); /* Full */ } else { tvhlog(LOG_DEBUG, "timeshift", "ts %d buffer full", ts->id); ts->full = 1; } } /* Create new file */ tsf_tmp = NULL; if (!ts->full) { /* Create directories */ if (!ts->path) { if (timeshift_filemgr_makedirs(ts->id, path, sizeof(path))) return NULL; ts->path = strdup(path); } /* Create File */ snprintf(path, sizeof(path), "%s/tvh-%"PRItime_t, ts->path, time); tvhtrace("timeshift", "ts %d create file %s", ts->id, path); if ((fd = open(path, O_WRONLY | O_CREAT, 0600)) > 0) { tsf_tmp = calloc(1, sizeof(timeshift_file_t)); tsf_tmp->time = time; tsf_tmp->fd = fd; tsf_tmp->path = strdup(path); tsf_tmp->refcount = 0; tsf_tmp->last = getmonoclock(); TAILQ_INIT(&tsf_tmp->iframes); TAILQ_INIT(&tsf_tmp->sstart); TAILQ_INSERT_TAIL(&ts->files, tsf_tmp, link); /* Copy across last start message */ if (tsf_tl && (ti = TAILQ_LAST(&tsf_tl->sstart, timeshift_index_data_list))) { tvhtrace("timeshift", "ts %d copy smt_start to new file", ts->id); timeshift_index_data_t *ti2 = calloc(1, sizeof(timeshift_index_data_t)); ti2->data = streaming_msg_clone(ti->data); TAILQ_INSERT_TAIL(&tsf_tmp->sstart, ti2, link); } } } tsf_tl = tsf_tmp; } if (tsf_tl) tsf_tl->refcount++; return tsf_tl; }
static int _timeshift_skip ( timeshift_t *ts, int64_t req_time, int64_t cur_time, timeshift_seek_t *seek, timeshift_seek_t *nseek ) { timeshift_index_iframe_t *tsi = seek->frame; timeshift_file_t *tsf = seek->file, *tsf_last; int64_t sec = mono2sec(req_time) / TIMESHIFT_FILE_PERIOD; int back = (req_time < cur_time) ? 1 : 0; int end = 0; /* Coarse search */ if (!tsi) { while (tsf && !end) { if (back) { if ((tsf->time <= sec) && (tsi = TAILQ_LAST(&tsf->iframes, timeshift_index_iframe_list))) break; tsf = timeshift_filemgr_prev(tsf, &end, 1); } else { if ((tsf->time >= sec) && (tsi = TAILQ_FIRST(&tsf->iframes))) break; tsf = timeshift_filemgr_next(tsf, &end, 0); } tsi = NULL; } } /* Fine search */ if (back) { while (!end && tsf && tsi && (tsi->time > req_time)) { tsi = TAILQ_PREV(tsi, timeshift_index_iframe_list, link); while (!end && tsf && !tsi) { if ((tsf = timeshift_filemgr_prev(tsf, &end, 1))) tsi = TAILQ_LAST(&tsf->iframes, timeshift_index_iframe_list); } } } else { while (!end && tsf && tsi && (tsi->time < req_time)) { tsi = TAILQ_NEXT(tsi, link); while (!end && tsf && !tsi) { if ((tsf = timeshift_filemgr_next(tsf, &end, 0))) tsi = TAILQ_FIRST(&tsf->iframes); } } } /* End */ if (!tsf || !tsi) end = 1; /* Find start/end of buffer */ if (end) { timeshift_file_put(tsf); if (back) { tsf = tsf_last = timeshift_filemgr_oldest(ts); tsi = NULL; while (tsf && !tsi) { tsf_last = tsf; if (!(tsi = TAILQ_FIRST(&tsf->iframes))) tsf = timeshift_filemgr_next(tsf, &end, 0); } if (!tsf) tsf = tsf_last; end = -1; } else { tsf = tsf_last = timeshift_filemgr_newest(ts); tsi = NULL; while (tsf && !tsi) { tsf_last = tsf; if (!(tsi = TAILQ_LAST(&tsf->iframes, timeshift_index_iframe_list))) tsf = timeshift_filemgr_prev(tsf, &end, 0); } if (!tsf) tsf = tsf_last; end = 1; } } /* Done */ nseek->file = tsf; nseek->frame = tsi; return end; }
/* * Get current / new file */ timeshift_file_t *timeshift_filemgr_get ( timeshift_t *ts, int create ) { int fd; struct timespec tp; timeshift_file_t *tsf_tl, *tsf_hd, *tsf_tmp; timeshift_index_data_t *ti; char path[PATH_MAX]; time_t time; /* Return last file */ if (!create) return timeshift_filemgr_newest(ts); /* No space */ if (ts->full) return NULL; /* Store to file */ clock_gettime(CLOCK_MONOTONIC_COARSE, &tp); time = tp.tv_sec / TIMESHIFT_FILE_PERIOD; tsf_tl = TAILQ_LAST(&ts->files, timeshift_file_list); if (!tsf_tl || tsf_tl->time != time || (tsf_tl->ram && tsf_tl->woff >= timeshift_conf.ram_segment_size)) { tsf_hd = TAILQ_FIRST(&ts->files); /* Close existing */ if (tsf_tl) timeshift_filemgr_close(tsf_tl); /* Check period */ if (!timeshift_conf.unlimited_period && ts->max_time && tsf_hd && tsf_tl) { time_t d = (tsf_tl->time - tsf_hd->time) * TIMESHIFT_FILE_PERIOD; if (d > (ts->max_time+5)) { if (!tsf_hd->refcount) { timeshift_filemgr_remove(ts, tsf_hd, 0); tsf_hd = NULL; } else { tvhlog(LOG_DEBUG, "timeshift", "ts %d buffer full", ts->id); ts->full = 1; } } } /* Check size */ if (!timeshift_conf.unlimited_size && atomic_pre_add_u64(×hift_conf.total_size, 0) >= timeshift_conf.max_size) { /* Remove the last file (if we can) */ if (tsf_hd && !tsf_hd->refcount) { timeshift_filemgr_remove(ts, tsf_hd, 0); /* Full */ } else { tvhlog(LOG_DEBUG, "timeshift", "ts %d buffer full", ts->id); ts->full = 1; } } /* Create new file */ tsf_tmp = NULL; if (!ts->full) { tvhtrace("timeshift", "ts %d RAM total %"PRId64" requested %"PRId64" segment %"PRId64, ts->id, atomic_pre_add_u64(×hift_total_ram_size, 0), timeshift_conf.ram_size, timeshift_conf.ram_segment_size); if (timeshift_conf.ram_size >= 8*1024*1024 && atomic_pre_add_u64(×hift_total_ram_size, 0) < timeshift_conf.ram_size + (timeshift_conf.ram_segment_size / 2)) { tsf_tmp = timeshift_filemgr_file_init(ts, time); tsf_tmp->ram_size = MIN(16*1024*1024, timeshift_conf.ram_segment_size); tsf_tmp->ram = malloc(tsf_tmp->ram_size); if (!tsf_tmp->ram) { free(tsf_tmp); tsf_tmp = NULL; } else { tvhtrace("timeshift", "ts %d create RAM segment with %"PRId64" bytes (time %li)", ts->id, tsf_tmp->ram_size, (long)time); } } if (!tsf_tmp && !timeshift_conf.ram_only) { /* Create directories */ if (!ts->path) { if (timeshift_filemgr_makedirs(ts->id, path, sizeof(path))) return NULL; ts->path = strdup(path); } /* Create File */ snprintf(path, sizeof(path), "%s/tvh-%"PRItime_t, ts->path, time); tvhtrace("timeshift", "ts %d create file %s", ts->id, path); if ((fd = open(path, O_WRONLY | O_CREAT, 0600)) > 0) { tsf_tmp = timeshift_filemgr_file_init(ts, time); tsf_tmp->wfd = fd; tsf_tmp->path = strdup(path); } } if (tsf_tmp) { /* Copy across last start message */ if (tsf_tl && (ti = TAILQ_LAST(&tsf_tl->sstart, timeshift_index_data_list))) { tvhtrace("timeshift", "ts %d copy smt_start to new file", ts->id); timeshift_index_data_t *ti2 = calloc(1, sizeof(timeshift_index_data_t)); ti2->data = streaming_msg_clone(ti->data); TAILQ_INSERT_TAIL(&tsf_tmp->sstart, ti2, link); } } } tsf_tl = tsf_tmp; } if (tsf_tl) tsf_tl->refcount++; return tsf_tl; }