From ed45ab72248a701788e151ec4e2843d2bc3619e8 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Fri, 14 Mar 2014 23:25:36 +0100 Subject: [PATCH] Add cache scheme selection for DVR to reduce system resources --- docs/html/config_dvr.html | 19 ++++++++++ src/dvr/dvr.h | 3 ++ src/dvr/dvr_db.c | 20 +++++++++++ src/dvr/dvr_rec.c | 1 + src/muxer.c | 72 +++++++++++++++++++++++++++++++++++++ src/muxer.h | 17 +++++++++ src/muxer/muxer_pass.c | 8 +++++ src/muxer/muxer_tvh.c | 2 +- src/muxer/tvh/mkmux.c | 8 ++++- src/muxer/tvh/mkmux.h | 2 +- src/webui/extjs.c | 45 +++++++++++++++++++++++ src/webui/static/app/dvr.js | 23 +++++++++++- 12 files changed, 216 insertions(+), 4 deletions(-) diff --git a/docs/html/config_dvr.html b/docs/html/config_dvr.html index 41294452..adce28bc 100644 --- a/docs/html/config_dvr.html +++ b/docs/html/config_dvr.html @@ -16,6 +16,25 @@
Media container
Select the container format used to store recordings. +
Cache scheme +
Select the cache scheme used to store recordings. + +
+ +
System +
Standard system caching. + +
Do not keep +
Do not keep the stored data in system's cache. + +
Sync +
Sync the stored data with medium (disk). + +
Sync + Do not keep +
Combination of two above variants. + +
+
Rewrite PAT in passthrough mode
Rewrite the original Program Association Table to only include the active service. When this option is disabled, Tvheadend will diff --git a/src/dvr/dvr.h b/src/dvr/dvr.h index f1d67a64..73b031f9 100644 --- a/src/dvr/dvr.h +++ b/src/dvr/dvr.h @@ -37,6 +37,7 @@ typedef struct dvr_config { int dvr_extra_time_post; muxer_container_type_t dvr_mc; + muxer_cache_type_t dvr_mux_cache; /* Series link support */ int dvr_sl_brand_lock; @@ -332,6 +333,8 @@ void dvr_storage_set(dvr_config_t *cfg, const char *storage); void dvr_container_set(dvr_config_t *cfg, const char *container); +void dvr_mux_cache_set(dvr_config_t *cfg, int mcache); + void dvr_postproc_set(dvr_config_t *cfg, const char *postproc); void dvr_retention_set(dvr_config_t *cfg, int days); diff --git a/src/dvr/dvr_db.c b/src/dvr/dvr_db.c index e80dae06..536e55a2 100644 --- a/src/dvr/dvr_db.c +++ b/src/dvr/dvr_db.c @@ -1113,6 +1113,7 @@ dvr_init(void) cfg = dvr_config_create(s); cfg->dvr_mc = htsmsg_get_u32_or_default(m, "container", MC_MATROSKA); + cfg->dvr_mux_cache = htsmsg_get_u32_or_default(m, "cache", MC_CACHE_DONTKEEP); if(!htsmsg_get_u32(m, "rewrite-pat", &u32)) { if (u32) @@ -1294,6 +1295,7 @@ dvr_config_create(const char *name) cfg->dvr_config_name = strdup(name); cfg->dvr_retention_days = 31; cfg->dvr_mc = MC_MATROSKA; + cfg->dvr_mux_cache = MC_CACHE_DONTKEEP; cfg->dvr_flags = DVR_TAG_FILES | DVR_SKIP_COMMERCIALS; /* series link support */ @@ -1352,6 +1354,7 @@ dvr_save(dvr_config_t *cfg) htsmsg_add_str(m, "config_name", cfg->dvr_config_name); htsmsg_add_str(m, "storage", cfg->dvr_storage); htsmsg_add_u32(m, "container", cfg->dvr_mc); + htsmsg_add_u32(m, "cache", cfg->dvr_mux_cache); htsmsg_add_u32(m, "rewrite-pat", !!(cfg->dvr_mux_flags & MUX_REWRITE_PAT)); htsmsg_add_u32(m, "rewrite-pmt", !!(cfg->dvr_mux_flags & MUX_REWRITE_PMT)); htsmsg_add_u32(m, "retention-days", cfg->dvr_retention_days); @@ -1412,6 +1415,23 @@ dvr_container_set(dvr_config_t *cfg, const char *container) dvr_save(cfg); } +/** + * + */ +void +dvr_mux_cache_set(dvr_config_t *cfg, int mcache) +{ + if (mcache < MC_CACHE_UNKNOWN || mcache > MC_CACHE_LAST) + mcache = MC_CACHE_UNKNOWN; + + if(cfg->dvr_mux_cache == mcache) + return; + + cfg->dvr_mux_cache = mcache; + + dvr_save(cfg); +} + /** * diff --git a/src/dvr/dvr_rec.c b/src/dvr/dvr_rec.c index 37fd0520..4b1b1132 100644 --- a/src/dvr/dvr_rec.c +++ b/src/dvr/dvr_rec.c @@ -293,6 +293,7 @@ dvr_rec_start(dvr_entry_t *de, const streaming_start_t *ss) mc = de->de_mc; m_cfg.dvr_flags = cfg->dvr_mux_flags; + m_cfg.dvr_cache = cfg->dvr_mux_cache; de->de_mux = muxer_create(mc, &m_cfg); if(!de->de_mux) { diff --git a/src/muxer.c b/src/muxer.c index f40e5144..2d737327 100644 --- a/src/muxer.c +++ b/src/muxer.c @@ -17,6 +17,7 @@ */ #include +#include #include "tvheadend.h" #include "service.h" @@ -253,6 +254,8 @@ muxer_create(muxer_container_type_t mc, muxer_config_t *m_cfg) if(!m) tvhlog(LOG_ERR, "mux", "Can't find a muxer that supports '%s' container", muxer_container_type2txt(mc)); + else + m->m_cache = m_cfg->dvr_cache; return m; } @@ -408,4 +411,73 @@ muxer_write_pkt(muxer_t *m, streaming_message_type_t smt, void *data) return m->m_write_pkt(m, smt, data); } +/** + * cache type conversions + */ +static struct strtab cache_types[] = { + { "Unknown", MC_CACHE_UNKNOWN }, + { "System", MC_CACHE_SYSTEM }, + { "Do not keep", MC_CACHE_DONTKEEP }, + { "Sync", MC_CACHE_SYNC }, + { "Sync + Do not keep", MC_CACHE_SYNCDONTKEEP } +}; +const char* +muxer_cache_type2txt(muxer_cache_type_t c) +{ + return val2str(c, cache_types); +} + +muxer_cache_type_t +muxer_cache_txt2type(const char *str) +{ + int r = str2val(str, cache_types); + if (r < 0) + r = MC_CACHE_UNKNOWN; + return r; +} + +/** + * cache scheme + */ +void +muxer_cache_update(muxer_t *m, int fd, off_t pos, size_t size) +{ + switch (m->m_cache) { + case MC_CACHE_UNKNOWN: + case MC_CACHE_SYSTEM: + break; + case MC_CACHE_SYNC: + fsync(fd); + break; + case MC_CACHE_SYNCDONTKEEP: + fsync(fd); + /* fall through */ + case MC_CACHE_DONTKEEP: + posix_fadvise(fd, pos, size, POSIX_FADV_DONTNEED); + break; + default: + abort(); + } +} + +/** + * Get a list of supported cache schemes + */ +int +muxer_cache_list(htsmsg_t *array) +{ + htsmsg_t *mc; + int c; + const char *s; + + for (c = 0; c <= MC_CACHE_LAST; c++) { + mc = htsmsg_create_map(); + s = muxer_cache_type2txt(c); + htsmsg_add_u32(mc, "index", c); + htsmsg_add_str(mc, "description", s); + htsmsg_add_msg(array, NULL, mc); + } + + return c; +} diff --git a/src/muxer.h b/src/muxer.h index 8215df69..c6518c73 100644 --- a/src/muxer.h +++ b/src/muxer.h @@ -34,9 +34,19 @@ typedef enum { MC_WEBM = 6, } muxer_container_type_t; +typedef enum { + MC_CACHE_UNKNOWN = 0, + MC_CACHE_SYSTEM = 1, + MC_CACHE_DONTKEEP = 2, + MC_CACHE_SYNC = 3, + MC_CACHE_SYNCDONTKEEP = 4, + MC_CACHE_LAST = MC_CACHE_SYNCDONTKEEP +} muxer_cache_type_t; + /* Muxer configuration used when creating a muxer. */ typedef struct muxer_config { int dvr_flags; + muxer_cache_type_t dvr_cache; } muxer_config_t; struct muxer; @@ -65,6 +75,7 @@ typedef struct muxer { int m_errors; // Number of errors muxer_container_type_t m_container; // The type of the container + muxer_cache_type_t m_cache; // Caching scheme } muxer_t; @@ -95,4 +106,10 @@ int muxer_write_pkt (muxer_t *m, streaming_message_type_t smt, void *d const char* muxer_mime (muxer_t *m, const struct streaming_start *ss); const char* muxer_suffix (muxer_t *m, const struct streaming_start *ss); +// Cache +const char * muxer_cache_type2txt(muxer_cache_type_t t); +muxer_cache_type_t muxer_cache_txt2type(const char *str); +void muxer_cache_update(muxer_t *m, int fd, off_t off, size_t size); +int muxer_cache_list(htsmsg_t *array); + #endif diff --git a/src/muxer/muxer_pass.c b/src/muxer/muxer_pass.c index ae9b781b..c31c8ec7 100644 --- a/src/muxer/muxer_pass.c +++ b/src/muxer/muxer_pass.c @@ -33,6 +33,7 @@ typedef struct pass_muxer { muxer_t; /* File descriptor stuff */ + off_t pm_off; int pm_fd; int pm_seekable; int pm_error; @@ -360,6 +361,7 @@ pass_muxer_open_stream(muxer_t *m, int fd) { pass_muxer_t *pm = (pass_muxer_t*)m; + pm->pm_off = 0; pm->pm_fd = fd; pm->pm_seekable = 0; pm->pm_filename = strdup("Live stream"); @@ -386,6 +388,7 @@ pass_muxer_open_file(muxer_t *m, const char *filename) return -1; } + pm->pm_off = 0; pm->pm_seekable = 1; pm->pm_fd = fd; pm->pm_filename = strdup(filename); @@ -408,6 +411,11 @@ pass_muxer_write(muxer_t *m, const void *data, size_t size) tvhlog(LOG_ERR, "pass", "%s: Write failed -- %s", pm->pm_filename, strerror(errno)); m->m_errors++; + muxer_cache_update(m, pm->pm_fd, pm->pm_off, 0); + pm->pm_off = lseek(pm->pm_fd, 0, SEEK_CUR); + } else { + muxer_cache_update(m, pm->pm_fd, pm->pm_off, 0); + pm->pm_off += size; } } diff --git a/src/muxer/muxer_tvh.c b/src/muxer/muxer_tvh.c index 1049c847..f87fa192 100644 --- a/src/muxer/muxer_tvh.c +++ b/src/muxer/muxer_tvh.c @@ -239,7 +239,7 @@ tvh_muxer_create(muxer_container_type_t mc, muxer_config_t *m_cfg) tm->m_close = tvh_muxer_close; tm->m_destroy = tvh_muxer_destroy; tm->m_container = mc; - tm->tm_ref = mk_mux_create(mc == MC_WEBM); + tm->tm_ref = mk_mux_create((muxer_t *)tm, mc == MC_WEBM); return (muxer_t*)tm; } diff --git a/src/muxer/tvh/mkmux.c b/src/muxer/tvh/mkmux.c index 4925039d..718704b2 100644 --- a/src/muxer/tvh/mkmux.c +++ b/src/muxer/tvh/mkmux.c @@ -85,6 +85,7 @@ typedef struct mk_chapter { * */ struct mk_mux { + muxer_t *m; int fd; char *filename; int error; @@ -424,6 +425,7 @@ mk_write_to_fd(mk_mux_t *mkm, htsbuf_queue_t *hq) { htsbuf_data_t *hd; int i = 0; + off_t oldpos = mkm->fdpos; TAILQ_FOREACH(hd, &hq->hq_q, hd_link) i++; @@ -448,6 +450,8 @@ mk_write_to_fd(mk_mux_t *mkm, htsbuf_queue_t *hq) iov += iovcnt; } while(i); + muxer_cache_update(mkm->m, mkm->fd, oldpos, 0); + return 0; } @@ -785,6 +789,7 @@ mk_write_metaseek(mk_mux_t *mkm, int first) mk_write_to_fd(mkm, &q); } else if(mkm->seekable) { off_t prev = mkm->fdpos; + mkm->fdpos = mkm->segment_pos; if(lseek(mkm->fd, mkm->segment_pos, SEEK_SET) == (off_t) -1) mkm->error = errno; @@ -1008,10 +1013,11 @@ mk_write_cues(mk_mux_t *mkm) /** * */ -mk_mux_t *mk_mux_create(int webm) +mk_mux_t *mk_mux_create(muxer_t *m, int webm) { mk_mux_t *mkm = calloc(1, sizeof(mk_mux_t)); + mkm->m = m; mkm->webm = webm; mkm->fd = -1; diff --git a/src/muxer/tvh/mkmux.h b/src/muxer/tvh/mkmux.h index 44a4de49..6840d3f1 100644 --- a/src/muxer/tvh/mkmux.h +++ b/src/muxer/tvh/mkmux.h @@ -28,7 +28,7 @@ struct th_pkt; struct channel; struct event; -mk_mux_t *mk_mux_create(int webm); +mk_mux_t *mk_mux_create(muxer_t *m, int webm); int mk_mux_open_file (mk_mux_t *mkm, const char *filename); int mk_mux_open_stream(mk_mux_t *mkm, int fd); diff --git a/src/webui/extjs.c b/src/webui/extjs.c index 443769db..e9af647c 100755 --- a/src/webui/extjs.c +++ b/src/webui/extjs.c @@ -622,6 +622,46 @@ skip: } +/** + * + */ +static int +extjs_dvr_caches(http_connection_t *hc, const char *remain, void *opaque) +{ + htsbuf_queue_t *hq = &hc->hc_reply; + const char *op = http_arg_get(&hc->hc_req_args, "op"); + htsmsg_t *out, *array; + + pthread_mutex_lock(&global_lock); + + if(op != NULL && !strcmp(op, "list")) { + + out = htsmsg_create_map(); + array = htsmsg_create_list(); + + if (http_access_verify(hc, ACCESS_RECORDER_ALL)) + goto skip; + + muxer_cache_list(array); + +skip: + htsmsg_add_msg(out, "entries", array); + + } else { + pthread_mutex_unlock(&global_lock); + return HTTP_STATUS_BAD_REQUEST; + } + + pthread_mutex_unlock(&global_lock); + + htsmsg_json_serialize(out, hq, 0); + htsmsg_destroy(out); + http_output_content(hc, "text/x-json; charset=UTF-8"); + return 0; + +} + + /** * */ @@ -1086,6 +1126,7 @@ extjs_dvr(http_connection_t *hc, const char *remain, void *opaque) r = htsmsg_create_map(); htsmsg_add_str(r, "storage", cfg->dvr_storage); htsmsg_add_str(r, "container", muxer_container_type2txt(cfg->dvr_mc)); + htsmsg_add_u32(r, "cache", cfg->dvr_mux_cache); htsmsg_add_u32(r, "rewritePAT", !!(cfg->dvr_mux_flags & MUX_REWRITE_PAT)); htsmsg_add_u32(r, "rewritePMT", !!(cfg->dvr_mux_flags & MUX_REWRITE_PMT)); if(cfg->dvr_postproc != NULL) @@ -1124,6 +1165,9 @@ extjs_dvr(http_connection_t *hc, const char *remain, void *opaque) if((s = http_arg_get(&hc->hc_req_args, "container")) != NULL) dvr_container_set(cfg,s); + if((s = http_arg_get(&hc->hc_req_args, "cache")) != NULL) + dvr_mux_cache_set(cfg,atoi(s)); + if((s = http_arg_get(&hc->hc_req_args, "postproc")) != NULL) dvr_postproc_set(cfg,s); @@ -1750,6 +1794,7 @@ extjs_start(void) http_path_add("/dvrlist_finished", NULL, extjs_dvrlist_finished, ACCESS_WEB_INTERFACE); http_path_add("/dvrlist_failed", NULL, extjs_dvrlist_failed, ACCESS_WEB_INTERFACE); http_path_add("/dvr_containers", NULL, extjs_dvr_containers, ACCESS_WEB_INTERFACE); + http_path_add("/dvr_caches", NULL, extjs_dvr_caches, ACCESS_WEB_INTERFACE); http_path_add("/ecglist", NULL, extjs_ecglist, ACCESS_WEB_INTERFACE); http_path_add("/config", NULL, extjs_config, ACCESS_WEB_INTERFACE); http_path_add("/languages", NULL, extjs_languages, ACCESS_WEB_INTERFACE); diff --git a/src/webui/static/app/dvr.js b/src/webui/static/app/dvr.js index c0131213..7a97a183 100644 --- a/src/webui/static/app/dvr.js +++ b/src/webui/static/app/dvr.js @@ -27,6 +27,18 @@ tvheadend.containers = new Ext.data.JsonStore({ } }); +//For the cache configuration +tvheadend.caches = new Ext.data.JsonStore({ + autoLoad : true, + root : 'entries', + fields : [ 'index', 'description' ], + id : 'name', + url : 'dvr_caches', + baseParams : { + op : 'list' + } +}); + /** * Configuration names @@ -733,7 +745,7 @@ tvheadend.dvrsettings = function() { var confreader = new Ext.data.JsonReader({ root : 'dvrSettings' }, [ 'storage', 'postproc', 'retention', 'dayDirs', 'channelDirs', - 'channelInTitle', 'container', 'dateInTitle', 'timeInTitle', + 'channelInTitle', 'container', 'cache', 'dateInTitle', 'timeInTitle', 'preExtraTime', 'postExtraTime', 'whitespaceInTitle', 'titleDirs', 'episodeInTitle', 'cleanTitle', 'tagFiles', 'commSkip', 'subtitleInTitle', 'episodeBeforeDate', 'rewritePAT', 'rewritePMT' ]); @@ -781,6 +793,15 @@ tvheadend.dvrsettings = function() { editable : false, width : 200, hiddenName : 'container' + }), new Ext.form.ComboBox({ + store : tvheadend.caches, + fieldLabel : 'Cache scheme', + triggerAction : 'all', + displayField : 'description', + valueField : 'index', + editable : false, + width : 200, + hiddenName : 'cache' }), new Ext.form.Checkbox({ fieldLabel : 'Rewrite PAT in passthrough mode', name : 'rewritePAT'