1 /**
2 * TV headend - Timeshift
3 * Copyright (C) 2012 Adam Sutton
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "tvheadend.h"
20 #include "streaming.h"
21 #include "timeshift.h"
22 #include "timeshift/private.h"
23 #include "config.h"
24 #include "settings.h"
25 #include "atomic.h"
26 #include "access.h"
27 #include "atomic.h"
28
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <unistd.h>
32 #include <fcntl.h>
33 #include <string.h>
34 #include <assert.h>
35 #include <stdio.h>
36
37 static int timeshift_index = 0;
38
39 struct timeshift_conf timeshift_conf;
40
41 memoryinfo_t timeshift_memoryinfo = { .my_name = "Timeshift" };
42 memoryinfo_t timeshift_memoryinfo_ram = { .my_name = "Timeshift RAM buffer" };
43
44 /*
45 * Packet log
46 */
47 void
timeshift_packet_log0(const char * source,timeshift_t * ts,streaming_message_t * sm)48 timeshift_packet_log0
49 ( const char *source, timeshift_t *ts, streaming_message_t *sm )
50 {
51 th_pkt_t *pkt = sm->sm_data;
52 tvhtrace(LS_TIMESHIFT,
53 "ts %d pkt %s - stream %d type %c pts %10"PRId64
54 " dts %10"PRId64" dur %10d len %6zu time %14"PRId64,
55 ts->id, source,
56 pkt->pkt_componentindex,
57 SCT_ISVIDEO(pkt->pkt_type) ? pkt_frametype_to_char(pkt->v.pkt_frametype) : '-',
58 ts_rescale(pkt->pkt_pts, 1000000),
59 ts_rescale(pkt->pkt_dts, 1000000),
60 pkt->pkt_duration,
61 pktbuf_len(pkt->pkt_payload),
62 sm->sm_time);
63 }
64
65 /*
66 * Safe values for RAM configuration
67 */
timeshift_fixup(void)68 static void timeshift_fixup ( void )
69 {
70 if (timeshift_conf.ram_only)
71 timeshift_conf.max_size = timeshift_conf.ram_size;
72 }
73
74 /*
75 * Intialise global file manager
76 */
timeshift_init(void)77 void timeshift_init ( void )
78 {
79 htsmsg_t *m;
80
81 memoryinfo_register(×hift_memoryinfo);
82 memoryinfo_register(×hift_memoryinfo_ram);
83
84 timeshift_filemgr_init();
85
86 /* Defaults */
87 memset(×hift_conf, 0, sizeof(timeshift_conf));
88 timeshift_conf.idnode.in_class = ×hift_conf_class;
89 timeshift_conf.max_period = 60; // Hr (60mins)
90 timeshift_conf.max_size = 10000 * (size_t)1048576; // 10G
91
92 idclass_register(×hift_conf_class);
93
94 /* Load settings */
95 if ((m = hts_settings_load("timeshift/config"))) {
96 idnode_load(×hift_conf.idnode, m);
97 htsmsg_destroy(m);
98 timeshift_fixup();
99 }
100 }
101
102 /*
103 * Terminate global file manager
104 */
timeshift_term(void)105 void timeshift_term ( void )
106 {
107 timeshift_filemgr_term();
108 free(timeshift_conf.path);
109 timeshift_conf.path = NULL;
110
111 memoryinfo_unregister(×hift_memoryinfo);
112 memoryinfo_unregister(×hift_memoryinfo_ram);
113 }
114
115 /*
116 * Changed settings
117 */
118 static void
timeshift_conf_class_changed(idnode_t * self)119 timeshift_conf_class_changed ( idnode_t *self )
120 {
121 timeshift_fixup();
122 }
123
124 /*
125 * Save settings
126 */
127 static htsmsg_t *
timeshift_conf_class_save(idnode_t * self,char * filename,size_t fsize)128 timeshift_conf_class_save ( idnode_t *self, char *filename, size_t fsize )
129 {
130 htsmsg_t *m = htsmsg_create_map();
131 idnode_save(×hift_conf.idnode, m);
132 snprintf(filename, fsize, "timeshift/config");
133 return m;
134 }
135
136 /*
137 * Class
138 */
139 static const void *
timeshift_conf_class_max_size_get(void * o)140 timeshift_conf_class_max_size_get ( void *o )
141 {
142 static uint64_t r;
143 r = timeshift_conf.max_size / 1048576LL;
144 return &r;
145 }
146
147 static int
timeshift_conf_class_max_size_set(void * o,const void * v)148 timeshift_conf_class_max_size_set ( void *o, const void *v )
149 {
150 uint64_t u64 = *(uint64_t *)v * 1048576LL;
151 if (u64 != timeshift_conf.max_size) {
152 timeshift_conf.max_size = u64;
153 return 1;
154 }
155 return 0;
156 }
157
158 static const void *
timeshift_conf_class_ram_size_get(void * o)159 timeshift_conf_class_ram_size_get ( void *o )
160 {
161 static uint64_t r;
162 r = timeshift_conf.ram_size / 1048576LL;
163 return &r;
164 }
165
166 static int
timeshift_conf_class_ram_size_set(void * o,const void * v)167 timeshift_conf_class_ram_size_set ( void *o, const void *v )
168 {
169 uint64_t u64 = *(uint64_t *)v * 1048576LL;
170 timeshift_conf.ram_segment_size = u64 / 10;
171 if (u64 != timeshift_conf.ram_size) {
172 timeshift_conf.ram_size = u64;
173 return 1;
174 }
175 return 0;
176 }
177
178 CLASS_DOC(timeshift)
179
180 const idclass_t timeshift_conf_class = {
181 .ic_snode = ×hift_conf.idnode,
182 .ic_class = "timeshift",
183 .ic_caption = N_("Timeshift"),
184 .ic_doc = tvh_doc_timeshift_class,
185 .ic_event = "timeshift",
186 .ic_perm_def = ACCESS_ADMIN,
187 .ic_changed = timeshift_conf_class_changed,
188 .ic_save = timeshift_conf_class_save,
189 .ic_properties = (const property_t[]){
190 {
191 .type = PT_BOOL,
192 .id = "enabled",
193 .name = N_("Enabled"),
194 .desc = N_("Enable/Disable timeshift."),
195 .off = offsetof(timeshift_conf_t, enabled),
196 },
197 {
198 .type = PT_BOOL,
199 .id = "ondemand",
200 .name = N_("On-demand (no first rewind)"),
201 .desc = N_("Only activate timeshift when the client makes the first "
202 "rewind, fast-forward or pause request. Note, "
203 "because there is no buffer on the first request "
204 "rewinding is not possible at that point."),
205 .off = offsetof(timeshift_conf_t, ondemand),
206 .opts = PO_EXPERT,
207 },
208 {
209 .type = PT_STR,
210 .id = "path",
211 .name = N_("Storage path"),
212 .desc = N_("Path to where the timeshift data will be stored. "
213 "If nothing is specified this will default to "
214 "CONF_DIR/timeshift/buffer."),
215 .off = offsetof(timeshift_conf_t, path),
216 .opts = PO_ADVANCED,
217 },
218 {
219 .type = PT_U32,
220 .id = "max_period",
221 .name = N_("Maximum period (mins)"),
222 .desc = N_("The maximum time period that will be buffered for "
223 "any given (client) subscription."),
224 .off = offsetof(timeshift_conf_t, max_period),
225 },
226 {
227 .type = PT_BOOL,
228 .id = "unlimited_period",
229 .name = N_("Unlimited time"),
230 .desc = N_("Allow the timeshift buffer to grow unbounded until "
231 "your storage media runs out of space. Warning, "
232 "enabling this option may cause your system to slow "
233 "down or crash completely!"),
234 .off = offsetof(timeshift_conf_t, unlimited_period),
235 .opts = PO_EXPERT,
236 },
237 {
238 .type = PT_S64,
239 .id = "max_size",
240 .name = N_("Maximum size (MB)"),
241 .desc = N_("The maximum combined size of all timeshift buffers. "
242 "If you specify an unlimited period it's highly "
243 "recommended you specify a value here."),
244 .set = timeshift_conf_class_max_size_set,
245 .get = timeshift_conf_class_max_size_get,
246 .opts = PO_ADVANCED,
247 },
248 {
249 .type = PT_S64,
250 .id = "ram_size",
251 .name = N_("Maximum RAM size (MB)"),
252 .desc = N_("The maximum RAM (system memory) size for timeshift "
253 "buffers. When free RAM buffers are available they "
254 "are used for timeshift data in preference to using "
255 "storage."),
256 .set = timeshift_conf_class_ram_size_set,
257 .get = timeshift_conf_class_ram_size_get,
258 .opts = PO_ADVANCED,
259 },
260 {
261 .type = PT_BOOL,
262 .id = "unlimited_size",
263 .name = N_("Unlimited size"),
264 .desc = N_("Allow the combined size of all timeshift buffers to "
265 "potentially grow unbounded until your storage media "
266 "runs out of space."),
267 .off = offsetof(timeshift_conf_t, unlimited_size),
268 .opts = PO_EXPERT,
269 },
270 {
271 .type = PT_BOOL,
272 .id = "ram_only",
273 .name = N_("RAM only"),
274 .desc = N_("Keep timeshift buffers in RAM only. "
275 "With this option enabled, the amount of rewind time "
276 "is limited by how much RAM TVHeadend is allowed."),
277 .off = offsetof(timeshift_conf_t, ram_only),
278 .opts = PO_ADVANCED,
279 },
280 {
281 .type = PT_BOOL,
282 .id = "ram_fit",
283 .name = N_("Fit to RAM (cut rewind)"),
284 .desc = N_("With \"RAM only\" enabled, and when \"Maximum RAM "
285 "size\" is reached, remove the oldest segment in the "
286 "buffer instead of replacing it completely. Note, "
287 "this may reduce the amount of rewind time."),
288 .off = offsetof(timeshift_conf_t, ram_fit),
289 .opts = PO_EXPERT,
290 },
291 {
292 .type = PT_BOOL,
293 .id = "teletext",
294 .name = N_("Include teletext"),
295 .desc = N_("Include teletext in the timeshift buffer. Enabling "
296 "this may cause issues with some services where the "
297 "teletext DTS is invalid."),
298 .off = offsetof(timeshift_conf_t, teletext),
299 .opts = PO_EXPERT,
300 },
301 {}
302 }
303 };
304
305 /*
306 * Process a packet
307 */
308
309 static int
timeshift_packet(timeshift_t * ts,streaming_message_t * sm)310 timeshift_packet( timeshift_t *ts, streaming_message_t *sm )
311 {
312 th_pkt_t *pkt = sm->sm_data;
313 int64_t time;
314
315 if (pkt->pkt_pts != PTS_UNSET) {
316 /* avoid to update last_wr_time for TELETEXT packets */
317 if (pkt->pkt_type != SCT_TELETEXT) {
318 time = ts_rescale(pkt->pkt_pts, 1000000);
319 if (ts->last_wr_time < time)
320 ts->last_wr_time = time;
321 }
322 }
323 sm->sm_time = ts->last_wr_time;
324 timeshift_packet_log("wr ", ts, sm);
325 streaming_target_deliver2(&ts->wr_queue.sq_st, sm);
326 return 0;
327 }
328
329 /*
330 * Receive data
331 */
timeshift_input(void * opaque,streaming_message_t * sm)332 static void timeshift_input
333 ( void *opaque, streaming_message_t *sm )
334 {
335 int type = sm->sm_type;
336 timeshift_t *ts = opaque;
337 th_pkt_t *pkt, *pkt2;
338
339 if (ts->exit)
340 return;
341
342 /* Control */
343 if (type == SMT_SKIP) {
344 timeshift_write_skip(ts->rd_pipe.wr, sm->sm_data);
345 streaming_msg_free(sm);
346 } else if (type == SMT_SPEED) {
347 timeshift_write_speed(ts->rd_pipe.wr, sm->sm_code);
348 streaming_msg_free(sm);
349 } else {
350
351 /* Change PTS/DTS offsets */
352 if (ts->packet_mode && ts->start_pts && type == SMT_PACKET) {
353 pkt = sm->sm_data;
354 pkt2 = pkt_copy_shallow(pkt);
355 pkt_ref_dec(pkt);
356 sm->sm_data = pkt2;
357 if (pkt2->pkt_pts != PTS_UNSET) pkt2->pkt_pts += ts->start_pts;
358 if (pkt2->pkt_dts != PTS_UNSET) pkt2->pkt_dts += ts->start_pts;
359 }
360
361 /* Check for exit */
362 else if (type == SMT_EXIT ||
363 (type == SMT_STOP && sm->sm_code != SM_CODE_SOURCE_RECONFIGURED))
364 ts->exit = 1;
365
366 else if (type == SMT_MPEGTS)
367 ts->packet_mode = 0;
368
369 /* Send to the writer thread */
370 if (ts->packet_mode) {
371 sm->sm_time = ts->last_wr_time;
372 if ((type == SMT_PACKET) && !timeshift_packet(ts, sm))
373 goto _exit;
374 } else {
375 if (ts->ref_time == 0) {
376 ts->ref_time = getfastmonoclock();
377 sm->sm_time = 0;
378 } else {
379 sm->sm_time = getfastmonoclock() - ts->ref_time;
380 }
381 }
382 streaming_target_deliver2(&ts->wr_queue.sq_st, sm);
383
384 /* Exit/Stop */
385 _exit:
386 if (ts->exit)
387 timeshift_write_exit(ts->rd_pipe.wr);
388 }
389 }
390
391 static htsmsg_t *
timeshift_input_info(void * opaque,htsmsg_t * list)392 timeshift_input_info(void *opaque, htsmsg_t *list)
393 {
394 htsmsg_add_str(list, NULL, "wtimeshift input");
395 return list;
396 }
397
398 static streaming_ops_t timeshift_input_ops = {
399 .st_cb = timeshift_input,
400 .st_info = timeshift_input_info
401 };
402
403
404 /**
405 *
406 */
407 void
timeshift_destroy(streaming_target_t * pad)408 timeshift_destroy(streaming_target_t *pad)
409 {
410 timeshift_t *ts = (timeshift_t*)pad;
411 streaming_message_t *sm;
412
413 /* Must hold global lock */
414 lock_assert(&global_lock);
415
416 /* Ensure the threads exits */
417 // Note: this is a workaround for the fact the Q might have been flushed
418 // in reader thread (VERY unlikely)
419 pthread_mutex_lock(&ts->state_mutex);
420 sm = streaming_msg_create(SMT_EXIT);
421 streaming_target_deliver2(&ts->wr_queue.sq_st, sm);
422 if (!ts->exit)
423 timeshift_write_exit(ts->rd_pipe.wr);
424 pthread_mutex_unlock(&ts->state_mutex);
425
426 /* Wait for all threads */
427 pthread_join(ts->rd_thread, NULL);
428 pthread_join(ts->wr_thread, NULL);
429
430 /* Shut stuff down */
431 streaming_queue_deinit(&ts->wr_queue);
432
433 close(ts->rd_pipe.rd);
434 close(ts->rd_pipe.wr);
435
436 /* Flush files */
437 timeshift_filemgr_flush(ts, NULL);
438
439 if (ts->smt_start)
440 streaming_start_unref(ts->smt_start);
441
442 if (ts->path)
443 free(ts->path);
444
445 free(ts);
446 memoryinfo_free(×hift_memoryinfo, sizeof(timeshift_t));
447 }
448
449 /**
450 * Create timeshift buffer
451 *
452 * max_period of buffer in seconds (0 = unlimited)
453 * max_size of buffer in bytes (0 = unlimited)
454 */
timeshift_create(streaming_target_t * out,time_t max_time)455 streaming_target_t *timeshift_create
456 (streaming_target_t *out, time_t max_time)
457 {
458 timeshift_t *ts = calloc(1, sizeof(timeshift_t));
459
460 memoryinfo_alloc(×hift_memoryinfo, sizeof(timeshift_t));
461
462 /* Must hold global lock */
463 lock_assert(&global_lock);
464
465 /* Setup structure */
466 TAILQ_INIT(&ts->files);
467 ts->output = out;
468 ts->path = NULL;
469 ts->max_time = max_time;
470 ts->state = TS_LIVE;
471 ts->exit = 0;
472 ts->full = 0;
473 ts->vididx = -1;
474 ts->id = timeshift_index;
475 ts->ondemand = timeshift_conf.ondemand;
476 ts->dobuf = ts->ondemand ? 0 : 1;
477 ts->packet_mode= 1;
478 ts->last_wr_time = 0;
479 ts->buf_time = 0;
480 ts->start_pts = 0;
481 ts->ref_time = 0;
482 ts->seek.file = NULL;
483 ts->seek.frame = NULL;
484 ts->ram_segments = 0;
485 ts->file_segments = 0;
486 pthread_mutex_init(&ts->state_mutex, NULL);
487
488 /* Initialise output */
489 tvh_pipe(O_NONBLOCK, &ts->rd_pipe);
490
491 /* Initialise input */
492 streaming_queue_init(&ts->wr_queue, 0, 0);
493 streaming_target_init(&ts->input, ×hift_input_ops, ts, 0);
494 tvhthread_create(&ts->wr_thread, NULL, timeshift_writer, ts, "tshift-wr");
495 tvhthread_create(&ts->rd_thread, NULL, timeshift_reader, ts, "tshift-rd");
496
497 /* Update index */
498 timeshift_index++;
499
500 return &ts->input;
501 }
502