Add stream profile support
This commit is contained in:
parent
29d5c75126
commit
2012dc8e3d
27 changed files with 1101 additions and 316 deletions
8
Makefile
8
Makefile
|
@ -122,7 +122,7 @@ SRCS = src/version.c \
|
||||||
src/trap.c \
|
src/trap.c \
|
||||||
src/avg.c \
|
src/avg.c \
|
||||||
src/htsstr.c \
|
src/htsstr.c \
|
||||||
src/tvhpoll.c \
|
src/tvhpoll.c \
|
||||||
src/huffman.c \
|
src/huffman.c \
|
||||||
src/filebundle.c \
|
src/filebundle.c \
|
||||||
src/config.c \
|
src/config.c \
|
||||||
|
@ -137,7 +137,8 @@ SRCS = src/version.c \
|
||||||
src/fsmonitor.c \
|
src/fsmonitor.c \
|
||||||
src/cron.c \
|
src/cron.c \
|
||||||
src/esfilter.c \
|
src/esfilter.c \
|
||||||
src/intlconv.c
|
src/intlconv.c \
|
||||||
|
src/profile.c
|
||||||
|
|
||||||
SRCS-${CONFIG_UPNP} += \
|
SRCS-${CONFIG_UPNP} += \
|
||||||
src/upnp.c
|
src/upnp.c
|
||||||
|
@ -157,7 +158,8 @@ SRCS += \
|
||||||
src/api/api_intlconv.c \
|
src/api/api_intlconv.c \
|
||||||
src/api/api_access.c \
|
src/api/api_access.c \
|
||||||
src/api/api_dvr.c \
|
src/api/api_dvr.c \
|
||||||
src/api/api_caclient.c
|
src/api/api_caclient.c \
|
||||||
|
src/api/api_profile.c
|
||||||
|
|
||||||
SRCS += \
|
SRCS += \
|
||||||
src/parsers/parsers.c \
|
src/parsers/parsers.c \
|
||||||
|
|
|
@ -134,6 +134,7 @@ void api_init ( void )
|
||||||
api_access_init();
|
api_access_init();
|
||||||
api_dvr_init();
|
api_dvr_init();
|
||||||
api_caclient_init();
|
api_caclient_init();
|
||||||
|
api_profile_init();
|
||||||
}
|
}
|
||||||
|
|
||||||
void api_done ( void )
|
void api_done ( void )
|
||||||
|
|
|
@ -74,6 +74,7 @@ void api_intlconv_init ( void );
|
||||||
void api_access_init ( void );
|
void api_access_init ( void );
|
||||||
void api_dvr_init ( void );
|
void api_dvr_init ( void );
|
||||||
void api_caclient_init ( void );
|
void api_caclient_init ( void );
|
||||||
|
void api_profile_init ( void );
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* IDnode
|
* IDnode
|
||||||
|
|
|
@ -34,6 +34,7 @@ api_caclient_list
|
||||||
htsmsg_t *l, *e;
|
htsmsg_t *l, *e;
|
||||||
|
|
||||||
l = htsmsg_create_list();
|
l = htsmsg_create_list();
|
||||||
|
pthread_mutex_lock(&global_lock);
|
||||||
TAILQ_FOREACH(cac, &caclients, cac_link) {
|
TAILQ_FOREACH(cac, &caclients, cac_link) {
|
||||||
e = htsmsg_create_map();
|
e = htsmsg_create_map();
|
||||||
htsmsg_add_str(e, "uuid", idnode_uuid_as_str(&cac->cac_id));
|
htsmsg_add_str(e, "uuid", idnode_uuid_as_str(&cac->cac_id));
|
||||||
|
@ -41,6 +42,7 @@ api_caclient_list
|
||||||
htsmsg_add_str(e, "status", caclient_get_status(cac));
|
htsmsg_add_str(e, "status", caclient_get_status(cac));
|
||||||
htsmsg_add_msg(l, NULL, e);
|
htsmsg_add_msg(l, NULL, e);
|
||||||
}
|
}
|
||||||
|
pthread_mutex_unlock(&global_lock);
|
||||||
*resp = htsmsg_create_map();
|
*resp = htsmsg_create_map();
|
||||||
htsmsg_add_msg(*resp, "entries", l);
|
htsmsg_add_msg(*resp, "entries", l);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
110
src/api/api_profile.c
Normal file
110
src/api/api_profile.c
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
/*
|
||||||
|
* tvheadend - API access to Stream Profile
|
||||||
|
*
|
||||||
|
* Copyright (C) 2014 Jaroslav Kysela
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "tvheadend.h"
|
||||||
|
#include "access.h"
|
||||||
|
#include "htsmsg.h"
|
||||||
|
#include "api.h"
|
||||||
|
#include "profile.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
api_profile_list
|
||||||
|
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
|
||||||
|
{
|
||||||
|
profile_t *pro;
|
||||||
|
htsmsg_t *l, *e;
|
||||||
|
|
||||||
|
l = htsmsg_create_list();
|
||||||
|
pthread_mutex_lock(&global_lock);
|
||||||
|
TAILQ_FOREACH(pro, &profiles, pro_link) {
|
||||||
|
e = htsmsg_create_map();
|
||||||
|
htsmsg_add_str(e, "key", idnode_uuid_as_str(&pro->pro_id));
|
||||||
|
htsmsg_add_str(e, "val", profile_get_name(pro));
|
||||||
|
htsmsg_add_msg(l, NULL, e);
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&global_lock);
|
||||||
|
*resp = htsmsg_create_map();
|
||||||
|
htsmsg_add_msg(*resp, "entries", l);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
api_profile_builders
|
||||||
|
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
|
||||||
|
{
|
||||||
|
profile_build_t *pb;
|
||||||
|
htsmsg_t *l, *e;
|
||||||
|
|
||||||
|
l = htsmsg_create_list();
|
||||||
|
|
||||||
|
pthread_mutex_lock(&global_lock);
|
||||||
|
/* List of available builder classes */
|
||||||
|
LIST_FOREACH(pb, &profile_builders, link)
|
||||||
|
if ((e = idclass_serialize(pb->clazz)))
|
||||||
|
htsmsg_add_msg(l, NULL, e);
|
||||||
|
pthread_mutex_unlock(&global_lock);
|
||||||
|
|
||||||
|
/* Output */
|
||||||
|
*resp = htsmsg_create_map();
|
||||||
|
htsmsg_add_msg(*resp, "entries", l);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
api_profile_create
|
||||||
|
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
|
||||||
|
{
|
||||||
|
int err = 0;
|
||||||
|
const char *clazz;
|
||||||
|
htsmsg_t *conf;
|
||||||
|
|
||||||
|
if (!(clazz = htsmsg_get_str(args, "class")))
|
||||||
|
return EINVAL;
|
||||||
|
if (!(conf = htsmsg_get_map(args, "conf")))
|
||||||
|
return EINVAL;
|
||||||
|
htsmsg_set_str(conf, "class", clazz);
|
||||||
|
|
||||||
|
pthread_mutex_lock(&global_lock);
|
||||||
|
if (profile_create(NULL, conf, 1) == NULL)
|
||||||
|
err = -EINVAL;
|
||||||
|
pthread_mutex_unlock(&global_lock);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Init
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
api_profile_init ( void )
|
||||||
|
{
|
||||||
|
static api_hook_t ah[] = {
|
||||||
|
{ "profile/list", ACCESS_ADMIN, api_profile_list, NULL },
|
||||||
|
{ "profile/class", ACCESS_ADMIN, api_idnode_class, (void*)&profile_class },
|
||||||
|
{ "profile/builders", ACCESS_ADMIN, api_profile_builders, NULL },
|
||||||
|
{ "profile/create", ACCESS_ADMIN, api_profile_create, NULL },
|
||||||
|
{ NULL },
|
||||||
|
};
|
||||||
|
|
||||||
|
api_register_all(ah);
|
||||||
|
}
|
|
@ -24,16 +24,19 @@
|
||||||
#include "channels.h"
|
#include "channels.h"
|
||||||
#include "subscriptions.h"
|
#include "subscriptions.h"
|
||||||
#include "muxer.h"
|
#include "muxer.h"
|
||||||
|
#include "profile.h"
|
||||||
#include "lang_str.h"
|
#include "lang_str.h"
|
||||||
|
|
||||||
typedef struct dvr_config {
|
typedef struct dvr_config {
|
||||||
idnode_t dvr_id;
|
idnode_t dvr_id;
|
||||||
|
|
||||||
LIST_ENTRY(dvr_config) config_link;
|
LIST_ENTRY(dvr_config) config_link;
|
||||||
|
LIST_ENTRY(dvr_config) profile_link;
|
||||||
|
|
||||||
int dvr_enabled;
|
int dvr_enabled;
|
||||||
int dvr_valid;
|
int dvr_valid;
|
||||||
char *dvr_config_name;
|
char *dvr_config_name;
|
||||||
|
profile_t *dvr_profile;
|
||||||
char *dvr_storage;
|
char *dvr_storage;
|
||||||
uint32_t dvr_retention_days;
|
uint32_t dvr_retention_days;
|
||||||
char *dvr_charset;
|
char *dvr_charset;
|
||||||
|
@ -43,7 +46,6 @@ typedef struct dvr_config {
|
||||||
uint32_t dvr_extra_time_post;
|
uint32_t dvr_extra_time_post;
|
||||||
uint32_t dvr_update_window;
|
uint32_t dvr_update_window;
|
||||||
|
|
||||||
int dvr_mc;
|
|
||||||
muxer_config_t dvr_muxcnf;
|
muxer_config_t dvr_muxcnf;
|
||||||
|
|
||||||
int dvr_dir_per_day;
|
int dvr_dir_per_day;
|
||||||
|
@ -208,15 +210,11 @@ typedef struct dvr_entry {
|
||||||
pthread_t de_thread;
|
pthread_t de_thread;
|
||||||
|
|
||||||
th_subscription_t *de_s;
|
th_subscription_t *de_s;
|
||||||
streaming_queue_t de_sq;
|
|
||||||
streaming_target_t *de_tsfix;
|
|
||||||
streaming_target_t *de_gh;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialized upon SUBSCRIPTION_TRANSPORT_RUN
|
|
||||||
*/
|
|
||||||
|
|
||||||
struct muxer *de_mux;
|
/**
|
||||||
|
* Stream worker chain
|
||||||
|
*/
|
||||||
|
profile_chain_t *de_chain;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inotify
|
* Inotify
|
||||||
|
@ -350,6 +348,8 @@ void dvr_config_delete(const char *name);
|
||||||
|
|
||||||
void dvr_config_save(dvr_config_t *cfg);
|
void dvr_config_save(dvr_config_t *cfg);
|
||||||
|
|
||||||
|
void dvr_config_destroy_by_profile(profile_t *pro, int delconf);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -139,7 +139,6 @@ dvr_config_create(const char *name, const char *uuid, htsmsg_t *conf)
|
||||||
cfg->dvr_enabled = 1;
|
cfg->dvr_enabled = 1;
|
||||||
cfg->dvr_config_name = strdup(name);
|
cfg->dvr_config_name = strdup(name);
|
||||||
cfg->dvr_retention_days = 31;
|
cfg->dvr_retention_days = 31;
|
||||||
cfg->dvr_mc = MC_MATROSKA;
|
|
||||||
cfg->dvr_tag_files = 1;
|
cfg->dvr_tag_files = 1;
|
||||||
cfg->dvr_skip_commercials = 1;
|
cfg->dvr_skip_commercials = 1;
|
||||||
dvr_charset_update(cfg, intlconv_filesystem_charset());
|
dvr_charset_update(cfg, intlconv_filesystem_charset());
|
||||||
|
@ -155,7 +154,6 @@ dvr_config_create(const char *name, const char *uuid, htsmsg_t *conf)
|
||||||
|
|
||||||
/* Muxer config */
|
/* Muxer config */
|
||||||
cfg->dvr_muxcnf.m_cache = MC_CACHE_DONTKEEP;
|
cfg->dvr_muxcnf.m_cache = MC_CACHE_DONTKEEP;
|
||||||
cfg->dvr_muxcnf.m_rewrite_pat = 1;
|
|
||||||
|
|
||||||
/* dup detect */
|
/* dup detect */
|
||||||
cfg->dvr_dup_detect_episode = 1; // detect dup episodes
|
cfg->dvr_dup_detect_episode = 1; // detect dup episodes
|
||||||
|
@ -172,6 +170,12 @@ dvr_config_create(const char *name, const char *uuid, htsmsg_t *conf)
|
||||||
|
|
||||||
tvhinfo("dvr", "Creating new configuration '%s'", cfg->dvr_config_name);
|
tvhinfo("dvr", "Creating new configuration '%s'", cfg->dvr_config_name);
|
||||||
|
|
||||||
|
if (cfg->dvr_profile == NULL) {
|
||||||
|
cfg->dvr_profile = profile_find_by_name(NULL);
|
||||||
|
assert(cfg->dvr_profile);
|
||||||
|
LIST_INSERT_HEAD(&cfg->dvr_profile->pro_dvr_configs, cfg, profile_link);
|
||||||
|
}
|
||||||
|
|
||||||
if (dvr_config_is_default(cfg) && dvr_config_find_by_name(NULL)) {
|
if (dvr_config_is_default(cfg) && dvr_config_find_by_name(NULL)) {
|
||||||
tvherror("dvr", "Unable to create second default config, removing");
|
tvherror("dvr", "Unable to create second default config, removing");
|
||||||
LIST_INSERT_HEAD(&dvrconfigs, cfg, config_link);
|
LIST_INSERT_HEAD(&dvrconfigs, cfg, config_link);
|
||||||
|
@ -201,6 +205,11 @@ dvr_config_destroy(dvr_config_t *cfg, int delconf)
|
||||||
LIST_REMOVE(cfg, config_link);
|
LIST_REMOVE(cfg, config_link);
|
||||||
idnode_unlink(&cfg->dvr_id);
|
idnode_unlink(&cfg->dvr_id);
|
||||||
|
|
||||||
|
if (cfg->dvr_profile) {
|
||||||
|
LIST_REMOVE(cfg, profile_link);
|
||||||
|
cfg->dvr_profile = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
dvr_entry_destroy_by_config(cfg, delconf);
|
dvr_entry_destroy_by_config(cfg, delconf);
|
||||||
access_destroy_by_dvr_config(cfg, delconf);
|
access_destroy_by_dvr_config(cfg, delconf);
|
||||||
autorec_destroy_by_config(cfg, delconf);
|
autorec_destroy_by_config(cfg, delconf);
|
||||||
|
@ -327,6 +336,51 @@ dvr_config_class_name_set(void *o, const void *v)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
dvr_config_class_profile_set(void *o, const void *v)
|
||||||
|
{
|
||||||
|
dvr_config_t *cfg = (dvr_config_t *)o;
|
||||||
|
profile_t *pro;
|
||||||
|
|
||||||
|
pro = v ? profile_find_by_uuid(v) : NULL;
|
||||||
|
pro = pro ?: profile_find_by_name(v);
|
||||||
|
if (pro == NULL) {
|
||||||
|
if (cfg->dvr_profile) {
|
||||||
|
LIST_REMOVE(cfg, profile_link);
|
||||||
|
cfg->dvr_profile = NULL;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
} else if (cfg->dvr_profile != pro) {
|
||||||
|
if (cfg->dvr_profile)
|
||||||
|
LIST_REMOVE(cfg, profile_link);
|
||||||
|
cfg->dvr_profile = pro;
|
||||||
|
LIST_INSERT_HEAD(&pro->pro_dvr_configs, cfg, profile_link);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const void *
|
||||||
|
dvr_config_class_profile_get(void *o)
|
||||||
|
{
|
||||||
|
static const char *ret;
|
||||||
|
dvr_config_t *cfg = (dvr_config_t *)o;
|
||||||
|
if (cfg->dvr_profile)
|
||||||
|
ret = idnode_uuid_as_str(&cfg->dvr_profile->pro_id);
|
||||||
|
else
|
||||||
|
ret = "";
|
||||||
|
return &ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *
|
||||||
|
dvr_config_class_profile_rend(void *o)
|
||||||
|
{
|
||||||
|
dvr_config_t *cfg = (dvr_config_t *)o;
|
||||||
|
if (cfg->dvr_profile)
|
||||||
|
return strdup(profile_get_name(cfg->dvr_profile));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static const char *
|
static const char *
|
||||||
dvr_config_class_get_title (idnode_t *self)
|
dvr_config_class_get_title (idnode_t *self)
|
||||||
{
|
{
|
||||||
|
@ -433,12 +487,14 @@ const idclass_t dvr_config_class = {
|
||||||
.get_opts = dvr_config_class_enabled_opts,
|
.get_opts = dvr_config_class_enabled_opts,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.type = PT_INT,
|
.type = PT_STR,
|
||||||
.id = "container",
|
.id = "profile",
|
||||||
.name = "Container",
|
.name = "Stream Profile",
|
||||||
.off = offsetof(dvr_config_t, dvr_mc),
|
.off = offsetof(dvr_config_t, dvr_profile),
|
||||||
.def.i = MC_MATROSKA,
|
.set = dvr_config_class_profile_set,
|
||||||
.list = dvr_entry_class_mc_list,
|
.get = dvr_config_class_profile_get,
|
||||||
|
.rend = dvr_config_class_profile_rend,
|
||||||
|
.list = profile_class_get_list,
|
||||||
.group = 1,
|
.group = 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -522,21 +578,6 @@ const idclass_t dvr_config_class = {
|
||||||
.def.s = "UTF-8",
|
.def.s = "UTF-8",
|
||||||
.group = 2,
|
.group = 2,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
.type = PT_BOOL,
|
|
||||||
.id = "rewrite-pat",
|
|
||||||
.name = "Rewrite PAT",
|
|
||||||
.off = offsetof(dvr_config_t, dvr_muxcnf.m_rewrite_pat),
|
|
||||||
.def.i = 1,
|
|
||||||
.group = 2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.type = PT_BOOL,
|
|
||||||
.id = "rewrite-pmt",
|
|
||||||
.name = "Rewrite PMT",
|
|
||||||
.off = offsetof(dvr_config_t, dvr_muxcnf.m_rewrite_pmt),
|
|
||||||
.group = 2,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
.type = PT_BOOL,
|
.type = PT_BOOL,
|
||||||
.id = "tag-files",
|
.id = "tag-files",
|
||||||
|
@ -649,6 +690,20 @@ const idclass_t dvr_config_class = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
dvr_config_destroy_by_profile(profile_t *pro, int delconf)
|
||||||
|
{
|
||||||
|
dvr_config_t *cfg;
|
||||||
|
|
||||||
|
while((cfg = LIST_FIRST(&pro->pro_dvr_configs)) != NULL) {
|
||||||
|
LIST_REMOVE(cfg, profile_link);
|
||||||
|
cfg->dvr_profile = profile_find_by_name(NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -103,7 +103,7 @@ dvr_entry_get_mc( dvr_entry_t *de )
|
||||||
{
|
{
|
||||||
if (de->de_mc >= 0)
|
if (de->de_mc >= 0)
|
||||||
return de->de_mc;
|
return de->de_mc;
|
||||||
return de->de_config->dvr_mc;
|
return profile_get_mc(de->de_config->dvr_profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
|
@ -1779,7 +1779,7 @@ const idclass_t dvr_entry_class = {
|
||||||
.def.i = MC_MATROSKA,
|
.def.i = MC_MATROSKA,
|
||||||
.set = dvr_entry_class_mc_set,
|
.set = dvr_entry_class_mc_set,
|
||||||
.list = dvr_entry_class_mc_list,
|
.list = dvr_entry_class_mc_list,
|
||||||
.opts = PO_SORTKEY
|
.opts = PO_RDONLY
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.type = PT_STR,
|
.type = PT_STR,
|
||||||
|
|
|
@ -63,10 +63,11 @@ dvr_rec_subscribe(dvr_entry_t *de)
|
||||||
{
|
{
|
||||||
char buf[100];
|
char buf[100];
|
||||||
int weight;
|
int weight;
|
||||||
streaming_target_t *st;
|
profile_t *pro;
|
||||||
int flags;
|
profile_chain_t *prch;
|
||||||
|
|
||||||
assert(de->de_s == NULL);
|
assert(de->de_s == NULL);
|
||||||
|
assert(de->de_chain == NULL);
|
||||||
|
|
||||||
if(de->de_pri < ARRAY_SIZE(prio2weight))
|
if(de->de_pri < ARRAY_SIZE(prio2weight))
|
||||||
weight = prio2weight[de->de_pri];
|
weight = prio2weight[de->de_pri];
|
||||||
|
@ -75,23 +76,28 @@ dvr_rec_subscribe(dvr_entry_t *de)
|
||||||
|
|
||||||
snprintf(buf, sizeof(buf), "DVR: %s", lang_str_get(de->de_title, NULL));
|
snprintf(buf, sizeof(buf), "DVR: %s", lang_str_get(de->de_title, NULL));
|
||||||
|
|
||||||
if(dvr_entry_get_mc(de) == MC_PASS) {
|
pro = de->de_config->dvr_profile;
|
||||||
streaming_queue_init(&de->de_sq, SMT_PACKET);
|
prch = malloc(sizeof(*prch));
|
||||||
de->de_gh = NULL;
|
if (pro->pro_open(pro, prch, &de->de_config->dvr_muxcnf, 0)) {
|
||||||
de->de_tsfix = NULL;
|
tvherror("dvr", "unable to create new channel streaming chain for '%s'",
|
||||||
st = &de->de_sq.sq_st;
|
channel_get_name(de->de_channel));
|
||||||
flags = SUBSCRIPTION_RAW_MPEGTS;
|
return;
|
||||||
} else {
|
|
||||||
streaming_queue_init(&de->de_sq, 0);
|
|
||||||
de->de_gh = globalheaders_create(&de->de_sq.sq_st);
|
|
||||||
st = de->de_tsfix = tsfix_create(de->de_gh);
|
|
||||||
tsfix_set_start_time(de->de_tsfix, dvr_entry_get_start_time(de));
|
|
||||||
flags = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
de->de_s = subscription_create_from_channel(de->de_channel, weight,
|
de->de_s = subscription_create_from_channel(de->de_channel, weight,
|
||||||
buf, st, flags,
|
buf, prch->prch_st,
|
||||||
|
prch->prch_flags,
|
||||||
NULL, NULL, NULL);
|
NULL, NULL, NULL);
|
||||||
|
if (de->de_s == NULL) {
|
||||||
|
tvherror("dvr", "unable to create new channel subcription for '%s'",
|
||||||
|
channel_get_name(de->de_channel));
|
||||||
|
profile_chain_close(prch);
|
||||||
|
free(prch);
|
||||||
|
de->de_chain = NULL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
de->de_chain = prch;
|
||||||
|
|
||||||
tvhthread_create(&de->de_thread, NULL, dvr_thread, de);
|
tvhthread_create(&de->de_thread, NULL, dvr_thread, de);
|
||||||
}
|
}
|
||||||
|
@ -102,20 +108,21 @@ dvr_rec_subscribe(dvr_entry_t *de)
|
||||||
void
|
void
|
||||||
dvr_rec_unsubscribe(dvr_entry_t *de, int stopcode)
|
dvr_rec_unsubscribe(dvr_entry_t *de, int stopcode)
|
||||||
{
|
{
|
||||||
assert(de->de_s != NULL);
|
profile_chain_t *prch = de->de_chain;
|
||||||
|
|
||||||
streaming_target_deliver(&de->de_sq.sq_st, streaming_msg_create(SMT_EXIT));
|
assert(de->de_s != NULL);
|
||||||
|
assert(prch != NULL);
|
||||||
|
|
||||||
|
streaming_target_deliver(prch->prch_st, streaming_msg_create(SMT_EXIT));
|
||||||
|
|
||||||
pthread_join(de->de_thread, NULL);
|
pthread_join(de->de_thread, NULL);
|
||||||
|
|
||||||
subscription_unsubscribe(de->de_s);
|
subscription_unsubscribe(de->de_s);
|
||||||
de->de_s = NULL;
|
de->de_s = NULL;
|
||||||
|
|
||||||
if(de->de_tsfix)
|
de->de_chain = NULL;
|
||||||
tsfix_destroy(de->de_tsfix);
|
profile_chain_close(prch);
|
||||||
|
free(prch);
|
||||||
if(de->de_gh)
|
|
||||||
globalheaders_destroy(de->de_gh);
|
|
||||||
|
|
||||||
de->de_last_error = stopcode;
|
de->de_last_error = stopcode;
|
||||||
}
|
}
|
||||||
|
@ -235,7 +242,7 @@ pvr_generate_filename(dvr_entry_t *de, const streaming_start_t *ss)
|
||||||
if (filename == NULL)
|
if (filename == NULL)
|
||||||
return -1;
|
return -1;
|
||||||
snprintf(fullname, sizeof(fullname), "%s/%s.%s",
|
snprintf(fullname, sizeof(fullname), "%s/%s.%s",
|
||||||
path, filename, muxer_suffix(de->de_mux, ss));
|
path, filename, muxer_suffix(de->de_chain->prch_muxer, ss));
|
||||||
|
|
||||||
while(1) {
|
while(1) {
|
||||||
if(stat(fullname, &st) == -1) {
|
if(stat(fullname, &st) == -1) {
|
||||||
|
@ -250,7 +257,7 @@ pvr_generate_filename(dvr_entry_t *de, const streaming_start_t *ss)
|
||||||
tally++;
|
tally++;
|
||||||
|
|
||||||
snprintf(fullname, sizeof(fullname), "%s/%s-%d.%s",
|
snprintf(fullname, sizeof(fullname), "%s/%s-%d.%s",
|
||||||
path, filename, tally, muxer_suffix(de->de_mux, ss));
|
path, filename, tally, muxer_suffix(de->de_chain->prch_muxer, ss));
|
||||||
}
|
}
|
||||||
free(filename);
|
free(filename);
|
||||||
|
|
||||||
|
@ -310,17 +317,23 @@ dvr_rec_start(dvr_entry_t *de, const streaming_start_t *ss)
|
||||||
const streaming_start_component_t *ssc;
|
const streaming_start_component_t *ssc;
|
||||||
int i;
|
int i;
|
||||||
dvr_config_t *cfg = de->de_config;
|
dvr_config_t *cfg = de->de_config;
|
||||||
muxer_container_type_t mc;
|
profile_chain_t *prch = de->de_chain;
|
||||||
|
muxer_t *muxer;
|
||||||
|
|
||||||
if (!cfg) {
|
if (!cfg) {
|
||||||
dvr_rec_fatal_error(de, "Unable to determine config profile");
|
dvr_rec_fatal_error(de, "Unable to determine config profile");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
mc = dvr_entry_get_mc(de);
|
if (!prch) {
|
||||||
|
dvr_rec_fatal_error(de, "Unable to determine stream profile");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
de->de_mux = muxer_create(mc, &cfg->dvr_muxcnf);
|
if (!(muxer = prch->prch_muxer))
|
||||||
if(!de->de_mux) {
|
muxer = prch->prch_muxer = muxer_create(&cfg->dvr_muxcnf);
|
||||||
|
|
||||||
|
if(!muxer) {
|
||||||
dvr_rec_fatal_error(de, "Unable to create muxer");
|
dvr_rec_fatal_error(de, "Unable to create muxer");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
@ -330,18 +343,18 @@ dvr_rec_start(dvr_entry_t *de, const streaming_start_t *ss)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(muxer_open_file(de->de_mux, de->de_filename)) {
|
if(muxer_open_file(muxer, de->de_filename)) {
|
||||||
dvr_rec_fatal_error(de, "Unable to open file");
|
dvr_rec_fatal_error(de, "Unable to open file");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(muxer_init(de->de_mux, ss, lang_str_get(de->de_title, NULL))) {
|
if(muxer_init(muxer, ss, lang_str_get(de->de_title, NULL))) {
|
||||||
dvr_rec_fatal_error(de, "Unable to init file");
|
dvr_rec_fatal_error(de, "Unable to init file");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(cfg->dvr_tag_files && de->de_bcast) {
|
if(cfg->dvr_tag_files && de->de_bcast) {
|
||||||
if(muxer_write_meta(de->de_mux, de->de_bcast)) {
|
if(muxer_write_meta(muxer, de->de_bcast)) {
|
||||||
dvr_rec_fatal_error(de, "Unable to write meta data");
|
dvr_rec_fatal_error(de, "Unable to write meta data");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
@ -438,7 +451,8 @@ dvr_thread(void *aux)
|
||||||
{
|
{
|
||||||
dvr_entry_t *de = aux;
|
dvr_entry_t *de = aux;
|
||||||
dvr_config_t *cfg = de->de_config;
|
dvr_config_t *cfg = de->de_config;
|
||||||
streaming_queue_t *sq = &de->de_sq;
|
profile_chain_t *prch = de->de_chain;
|
||||||
|
streaming_queue_t *sq = &prch->prch_sq;
|
||||||
streaming_message_t *sm;
|
streaming_message_t *sm;
|
||||||
th_pkt_t *pkt;
|
th_pkt_t *pkt;
|
||||||
int run = 1;
|
int run = 1;
|
||||||
|
@ -482,12 +496,12 @@ dvr_thread(void *aux)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if(commercial != pkt->pkt_commercial)
|
if(commercial != pkt->pkt_commercial)
|
||||||
muxer_add_marker(de->de_mux);
|
muxer_add_marker(prch->prch_muxer);
|
||||||
|
|
||||||
commercial = pkt->pkt_commercial;
|
commercial = pkt->pkt_commercial;
|
||||||
|
|
||||||
if(started) {
|
if(started) {
|
||||||
muxer_write_pkt(de->de_mux, sm->sm_type, sm->sm_data);
|
muxer_write_pkt(prch->prch_muxer, sm->sm_type, sm->sm_data);
|
||||||
sm->sm_data = NULL;
|
sm->sm_data = NULL;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -495,14 +509,14 @@ dvr_thread(void *aux)
|
||||||
case SMT_MPEGTS:
|
case SMT_MPEGTS:
|
||||||
if(started) {
|
if(started) {
|
||||||
dvr_rec_set_state(de, DVR_RS_RUNNING, 0);
|
dvr_rec_set_state(de, DVR_RS_RUNNING, 0);
|
||||||
muxer_write_pkt(de->de_mux, sm->sm_type, sm->sm_data);
|
muxer_write_pkt(prch->prch_muxer, sm->sm_type, sm->sm_data);
|
||||||
sm->sm_data = NULL;
|
sm->sm_data = NULL;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SMT_START:
|
case SMT_START:
|
||||||
if(started &&
|
if(started &&
|
||||||
muxer_reconfigure(de->de_mux, sm->sm_data) < 0) {
|
muxer_reconfigure(prch->prch_muxer, sm->sm_data) < 0) {
|
||||||
tvhlog(LOG_WARNING,
|
tvhlog(LOG_WARNING,
|
||||||
"dvr", "Unable to reconfigure \"%s\"",
|
"dvr", "Unable to reconfigure \"%s\"",
|
||||||
de->de_filename ?: lang_str_get(de->de_title, NULL));
|
de->de_filename ?: lang_str_get(de->de_title, NULL));
|
||||||
|
@ -607,7 +621,7 @@ dvr_thread(void *aux)
|
||||||
}
|
}
|
||||||
pthread_mutex_unlock(&sq->sq_mutex);
|
pthread_mutex_unlock(&sq->sq_mutex);
|
||||||
|
|
||||||
if(de->de_mux)
|
if(prch->prch_muxer)
|
||||||
dvr_thread_epilog(de);
|
dvr_thread_epilog(de);
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -671,9 +685,11 @@ dvr_spawn_postproc(dvr_entry_t *de, const char *dvr_postproc)
|
||||||
static void
|
static void
|
||||||
dvr_thread_epilog(dvr_entry_t *de)
|
dvr_thread_epilog(dvr_entry_t *de)
|
||||||
{
|
{
|
||||||
muxer_close(de->de_mux);
|
profile_chain_t *prch = de->de_chain;
|
||||||
muxer_destroy(de->de_mux);
|
|
||||||
de->de_mux = NULL;
|
muxer_close(prch->prch_muxer);
|
||||||
|
muxer_destroy(prch->prch_muxer);
|
||||||
|
prch->prch_muxer = NULL;
|
||||||
|
|
||||||
dvr_config_t *cfg = de->de_config;
|
dvr_config_t *cfg = de->de_config;
|
||||||
if(cfg && cfg->dvr_postproc && de->de_filename)
|
if(cfg && cfg->dvr_postproc && de->de_filename)
|
||||||
|
|
|
@ -69,6 +69,7 @@
|
||||||
#include "libav.h"
|
#include "libav.h"
|
||||||
#include "plumbing/transcoding.h"
|
#include "plumbing/transcoding.h"
|
||||||
#endif
|
#endif
|
||||||
|
#include "profile.h"
|
||||||
|
|
||||||
#ifdef PLATFORM_LINUX
|
#ifdef PLATFORM_LINUX
|
||||||
#include <sys/prctl.h>
|
#include <sys/prctl.h>
|
||||||
|
@ -816,6 +817,8 @@ main(int argc, char **argv)
|
||||||
transcoding_init();
|
transcoding_init();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
profile_init();
|
||||||
|
|
||||||
imagecache_init();
|
imagecache_init();
|
||||||
|
|
||||||
http_client_init(opt_user_agent);
|
http_client_init(opt_user_agent);
|
||||||
|
@ -939,6 +942,7 @@ main(int argc, char **argv)
|
||||||
tvhftrace("main", dvb_done);
|
tvhftrace("main", dvb_done);
|
||||||
tvhftrace("main", lang_str_done);
|
tvhftrace("main", lang_str_done);
|
||||||
tvhftrace("main", esfilter_done);
|
tvhftrace("main", esfilter_done);
|
||||||
|
tvhftrace("main", profile_done);
|
||||||
tvhftrace("main", intlconv_done);
|
tvhftrace("main", intlconv_done);
|
||||||
tvhftrace("main", urlparse_done);
|
tvhftrace("main", urlparse_done);
|
||||||
tvhftrace("main", idnode_done);
|
tvhftrace("main", idnode_done);
|
||||||
|
|
12
src/muxer.c
12
src/muxer.c
|
@ -159,6 +159,7 @@ muxer_container_type2txt(muxer_container_type_t mc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if 0
|
||||||
/**
|
/**
|
||||||
* Get a list of supported containers
|
* Get a list of supported containers
|
||||||
*/
|
*/
|
||||||
|
@ -190,6 +191,7 @@ muxer_container_list(htsmsg_t *array)
|
||||||
|
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -237,25 +239,25 @@ muxer_container_mime2type(const char *str)
|
||||||
* Create a new muxer
|
* Create a new muxer
|
||||||
*/
|
*/
|
||||||
muxer_t*
|
muxer_t*
|
||||||
muxer_create(muxer_container_type_t mc, const muxer_config_t *m_cfg)
|
muxer_create(const muxer_config_t *m_cfg)
|
||||||
{
|
{
|
||||||
muxer_t *m;
|
muxer_t *m;
|
||||||
|
|
||||||
assert(m_cfg);
|
assert(m_cfg);
|
||||||
|
|
||||||
m = pass_muxer_create(mc, m_cfg);
|
m = pass_muxer_create(m_cfg);
|
||||||
|
|
||||||
if(!m)
|
if(!m)
|
||||||
m = tvh_muxer_create(mc, m_cfg);
|
m = tvh_muxer_create(m_cfg);
|
||||||
|
|
||||||
#if CONFIG_LIBAV
|
#if CONFIG_LIBAV
|
||||||
if(!m)
|
if(!m)
|
||||||
m = lav_muxer_create(mc, m_cfg);
|
m = lav_muxer_create(m_cfg);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if(!m) {
|
if(!m) {
|
||||||
tvhlog(LOG_ERR, "mux", "Can't find a muxer that supports '%s' container",
|
tvhlog(LOG_ERR, "mux", "Can't find a muxer that supports '%s' container",
|
||||||
muxer_container_type2txt(mc));
|
muxer_container_type2txt(m_cfg->m_type));
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,8 @@ typedef enum {
|
||||||
|
|
||||||
/* Muxer configuration used when creating a muxer. */
|
/* Muxer configuration used when creating a muxer. */
|
||||||
typedef struct muxer_config {
|
typedef struct muxer_config {
|
||||||
|
int m_type; /* MC_* */
|
||||||
|
|
||||||
int m_rewrite_pat;
|
int m_rewrite_pat;
|
||||||
int m_rewrite_pmt;
|
int m_rewrite_pmt;
|
||||||
int m_cache;
|
int m_cache;
|
||||||
|
@ -83,7 +85,6 @@ typedef struct muxer {
|
||||||
|
|
||||||
int m_eos; // End of stream
|
int m_eos; // End of stream
|
||||||
int m_errors; // Number of errors
|
int m_errors; // Number of errors
|
||||||
muxer_container_type_t m_container; // The type of the container
|
|
||||||
muxer_config_t m_config; // general configuration
|
muxer_config_t m_config; // general configuration
|
||||||
} muxer_t;
|
} muxer_t;
|
||||||
|
|
||||||
|
@ -97,10 +98,10 @@ muxer_container_type_t muxer_container_mime2type (const char *str);
|
||||||
|
|
||||||
const char* muxer_container_suffix(muxer_container_type_t mc, int video);
|
const char* muxer_container_suffix(muxer_container_type_t mc, int video);
|
||||||
|
|
||||||
int muxer_container_list(htsmsg_t *array);
|
//int muxer_container_list(htsmsg_t *array);
|
||||||
|
|
||||||
// Muxer factory
|
// Muxer factory
|
||||||
muxer_t *muxer_create(muxer_container_type_t mc, const muxer_config_t *m_cfg);
|
muxer_t *muxer_create(const muxer_config_t *m_cfg);
|
||||||
|
|
||||||
// Wrapper functions
|
// Wrapper functions
|
||||||
int muxer_open_file (muxer_t *m, const char *filename);
|
int muxer_open_file (muxer_t *m, const char *filename);
|
||||||
|
|
|
@ -75,7 +75,7 @@ lav_muxer_add_stream(lav_muxer_t *lm,
|
||||||
c = st->codec;
|
c = st->codec;
|
||||||
c->codec_id = streaming_component_type2codec_id(ssc->ssc_type);
|
c->codec_id = streaming_component_type2codec_id(ssc->ssc_type);
|
||||||
|
|
||||||
switch(lm->m_container) {
|
switch(lm->m_config.m_type) {
|
||||||
case MC_MATROSKA:
|
case MC_MATROSKA:
|
||||||
st->time_base.num = 1000000;
|
st->time_base.num = 1000000;
|
||||||
st->time_base.den = 1;
|
st->time_base.den = 1;
|
||||||
|
@ -205,7 +205,7 @@ lav_muxer_mime(muxer_t* m, const struct streaming_start *ss)
|
||||||
if(ssc->ssc_disabled)
|
if(ssc->ssc_disabled)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if(!lav_muxer_support_stream(m->m_container, ssc->ssc_type))
|
if(!lav_muxer_support_stream(m->m_config.m_type, ssc->ssc_type))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
has_video |= SCT_ISVIDEO(ssc->ssc_type);
|
has_video |= SCT_ISVIDEO(ssc->ssc_type);
|
||||||
|
@ -213,9 +213,9 @@ lav_muxer_mime(muxer_t* m, const struct streaming_start *ss)
|
||||||
}
|
}
|
||||||
|
|
||||||
if(has_video)
|
if(has_video)
|
||||||
return muxer_container_type2mime(m->m_container, 1);
|
return muxer_container_type2mime(m->m_config.m_type, 1);
|
||||||
else if(has_audio)
|
else if(has_audio)
|
||||||
return muxer_container_type2mime(m->m_container, 0);
|
return muxer_container_type2mime(m->m_config.m_type, 0);
|
||||||
else
|
else
|
||||||
return muxer_container_type2mime(MC_UNKNOWN, 0);
|
return muxer_container_type2mime(MC_UNKNOWN, 0);
|
||||||
}
|
}
|
||||||
|
@ -241,7 +241,7 @@ lav_muxer_init(muxer_t* m, const struct streaming_start *ss, const char *name)
|
||||||
av_dict_set(&oc->metadata, "service_name", name, 0);
|
av_dict_set(&oc->metadata, "service_name", name, 0);
|
||||||
av_dict_set(&oc->metadata, "service_provider", app, 0);
|
av_dict_set(&oc->metadata, "service_provider", app, 0);
|
||||||
|
|
||||||
if(lm->m_container == MC_MPEGTS)
|
if(lm->m_config.m_type == MC_MPEGTS)
|
||||||
lm->lm_h264_filter = av_bitstream_filter_init("h264_mp4toannexb");
|
lm->lm_h264_filter = av_bitstream_filter_init("h264_mp4toannexb");
|
||||||
|
|
||||||
oc->max_delay = 0.7 * AV_TIME_BASE;
|
oc->max_delay = 0.7 * AV_TIME_BASE;
|
||||||
|
@ -252,10 +252,10 @@ lav_muxer_init(muxer_t* m, const struct streaming_start *ss, const char *name)
|
||||||
if(ssc->ssc_disabled)
|
if(ssc->ssc_disabled)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if(!lav_muxer_support_stream(lm->m_container, ssc->ssc_type)) {
|
if(!lav_muxer_support_stream(lm->m_config.m_type, ssc->ssc_type)) {
|
||||||
tvhlog(LOG_WARNING, "libav", "%s is not supported in %s",
|
tvhlog(LOG_WARNING, "libav", "%s is not supported in %s",
|
||||||
streaming_component_type2txt(ssc->ssc_type),
|
streaming_component_type2txt(ssc->ssc_type),
|
||||||
muxer_container_type2txt(lm->m_container));
|
muxer_container_type2txt(lm->m_config.m_type));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,7 +272,7 @@ lav_muxer_init(muxer_t* m, const struct streaming_start *ss, const char *name)
|
||||||
return -1;
|
return -1;
|
||||||
} else if(avformat_write_header(lm->lm_oc, NULL) < 0) {
|
} else if(avformat_write_header(lm->lm_oc, NULL) < 0) {
|
||||||
tvhlog(LOG_ERR, "libav", "Failed to write %s header",
|
tvhlog(LOG_ERR, "libav", "Failed to write %s header",
|
||||||
muxer_container_type2txt(lm->m_container));
|
muxer_container_type2txt(lm->m_config.m_type));
|
||||||
lm->m_errors++;
|
lm->m_errors++;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
@ -454,7 +454,7 @@ lav_muxer_close(muxer_t *m)
|
||||||
|
|
||||||
if(lm->lm_init && av_write_trailer(lm->lm_oc) < 0) {
|
if(lm->lm_init && av_write_trailer(lm->lm_oc) < 0) {
|
||||||
tvhlog(LOG_WARNING, "libav", "Failed to write %s trailer",
|
tvhlog(LOG_WARNING, "libav", "Failed to write %s trailer",
|
||||||
muxer_container_type2txt(lm->m_container));
|
muxer_container_type2txt(lm->m_config.m_type));
|
||||||
lm->m_errors++;
|
lm->m_errors++;
|
||||||
ret = -1;
|
ret = -1;
|
||||||
}
|
}
|
||||||
|
@ -493,18 +493,18 @@ lav_muxer_destroy(muxer_t *m)
|
||||||
* Create a new libavformat based muxer
|
* Create a new libavformat based muxer
|
||||||
*/
|
*/
|
||||||
muxer_t*
|
muxer_t*
|
||||||
lav_muxer_create(muxer_container_type_t mc, const muxer_config_t *m_cfg)
|
lav_muxer_create(const muxer_config_t *m_cfg)
|
||||||
{
|
{
|
||||||
const char *mux_name;
|
const char *mux_name;
|
||||||
lav_muxer_t *lm;
|
lav_muxer_t *lm;
|
||||||
AVOutputFormat *fmt;
|
AVOutputFormat *fmt;
|
||||||
|
|
||||||
switch(mc) {
|
switch(m_cfg->m_type) {
|
||||||
case MC_MPEGPS:
|
case MC_MPEGPS:
|
||||||
mux_name = "dvd";
|
mux_name = "dvd";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
mux_name = muxer_container_type2txt(mc);
|
mux_name = muxer_container_type2txt(m_cfg->m_type);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -525,7 +525,6 @@ lav_muxer_create(muxer_container_type_t mc, const muxer_config_t *m_cfg)
|
||||||
lm->m_write_pkt = lav_muxer_write_pkt;
|
lm->m_write_pkt = lav_muxer_write_pkt;
|
||||||
lm->m_close = lav_muxer_close;
|
lm->m_close = lav_muxer_close;
|
||||||
lm->m_destroy = lav_muxer_destroy;
|
lm->m_destroy = lav_muxer_destroy;
|
||||||
lm->m_container = mc;
|
|
||||||
lm->lm_oc = avformat_alloc_context();
|
lm->lm_oc = avformat_alloc_context();
|
||||||
lm->lm_oc->oformat = fmt;
|
lm->lm_oc->oformat = fmt;
|
||||||
lm->lm_fd = -1;
|
lm->lm_fd = -1;
|
||||||
|
|
|
@ -21,7 +21,6 @@
|
||||||
|
|
||||||
#include "muxer.h"
|
#include "muxer.h"
|
||||||
|
|
||||||
muxer_t* lav_muxer_create
|
muxer_t* lav_muxer_create (const muxer_config_t* m_cfg);
|
||||||
(muxer_container_type_t mc, const muxer_config_t* m_cfg);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -562,11 +562,11 @@ pass_muxer_destroy(muxer_t *m)
|
||||||
* Create a new passthrough muxer
|
* Create a new passthrough muxer
|
||||||
*/
|
*/
|
||||||
muxer_t*
|
muxer_t*
|
||||||
pass_muxer_create(muxer_container_type_t mc, const muxer_config_t *m_cfg)
|
pass_muxer_create(const muxer_config_t *m_cfg)
|
||||||
{
|
{
|
||||||
pass_muxer_t *pm;
|
pass_muxer_t *pm;
|
||||||
|
|
||||||
if(mc != MC_PASS && mc != MC_RAW)
|
if(m_cfg->m_type != MC_PASS && m_cfg->m_type != MC_RAW)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
pm = calloc(1, sizeof(pass_muxer_t));
|
pm = calloc(1, sizeof(pass_muxer_t));
|
||||||
|
|
|
@ -21,7 +21,6 @@
|
||||||
|
|
||||||
#include "muxer.h"
|
#include "muxer.h"
|
||||||
|
|
||||||
muxer_t* pass_muxer_create
|
muxer_t* pass_muxer_create (const muxer_config_t* m_cfg);
|
||||||
(muxer_container_type_t mc, const muxer_config_t* m_cfg);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -56,9 +56,9 @@ tvh_muxer_mime(muxer_t* m, const struct streaming_start *ss)
|
||||||
}
|
}
|
||||||
|
|
||||||
if(has_video)
|
if(has_video)
|
||||||
return muxer_container_type2mime(m->m_container, 1);
|
return muxer_container_type2mime(m->m_config.m_type, 1);
|
||||||
else if(has_audio)
|
else if(has_audio)
|
||||||
return muxer_container_type2mime(m->m_container, 0);
|
return muxer_container_type2mime(m->m_config.m_type, 0);
|
||||||
else
|
else
|
||||||
return muxer_container_type2mime(MC_UNKNOWN, 0);
|
return muxer_container_type2mime(MC_UNKNOWN, 0);
|
||||||
}
|
}
|
||||||
|
@ -223,11 +223,11 @@ tvh_muxer_destroy(muxer_t *m)
|
||||||
* Create a new builtin muxer
|
* Create a new builtin muxer
|
||||||
*/
|
*/
|
||||||
muxer_t*
|
muxer_t*
|
||||||
tvh_muxer_create(muxer_container_type_t mc, const muxer_config_t *m_cfg)
|
tvh_muxer_create(const muxer_config_t *m_cfg)
|
||||||
{
|
{
|
||||||
tvh_muxer_t *tm;
|
tvh_muxer_t *tm;
|
||||||
|
|
||||||
if(mc != MC_MATROSKA && mc != MC_WEBM)
|
if(m_cfg->m_type != MC_MATROSKA && m_cfg->m_type != MC_WEBM)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
tm = calloc(1, sizeof(tvh_muxer_t));
|
tm = calloc(1, sizeof(tvh_muxer_t));
|
||||||
|
@ -241,8 +241,7 @@ tvh_muxer_create(muxer_container_type_t mc, const muxer_config_t *m_cfg)
|
||||||
tm->m_write_pkt = tvh_muxer_write_pkt;
|
tm->m_write_pkt = tvh_muxer_write_pkt;
|
||||||
tm->m_close = tvh_muxer_close;
|
tm->m_close = tvh_muxer_close;
|
||||||
tm->m_destroy = tvh_muxer_destroy;
|
tm->m_destroy = tvh_muxer_destroy;
|
||||||
tm->m_container = mc;
|
tm->tm_ref = mk_mux_create((muxer_t *)tm, m_cfg->m_type == MC_WEBM);
|
||||||
tm->tm_ref = mk_mux_create((muxer_t *)tm, mc == MC_WEBM);
|
|
||||||
|
|
||||||
return (muxer_t*)tm;
|
return (muxer_t*)tm;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,6 @@
|
||||||
|
|
||||||
#include "muxer.h"
|
#include "muxer.h"
|
||||||
|
|
||||||
muxer_t* tvh_muxer_create
|
muxer_t* tvh_muxer_create (const muxer_config_t* m_cfg);
|
||||||
(muxer_container_type_t mc, const muxer_config_t* m_cfg);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
541
src/profile.c
Normal file
541
src/profile.c
Normal file
|
@ -0,0 +1,541 @@
|
||||||
|
/*
|
||||||
|
* tvheadend, Stream Profile
|
||||||
|
* Copyright (C) 2014 Jaroslav Kysela
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "tvheadend.h"
|
||||||
|
#include "settings.h"
|
||||||
|
#include "profile.h"
|
||||||
|
#include "streaming.h"
|
||||||
|
#include "plumbing/tsfix.h"
|
||||||
|
#include "plumbing/globalheaders.h"
|
||||||
|
#include "dvr/dvr.h"
|
||||||
|
|
||||||
|
profile_builders_queue profile_builders;
|
||||||
|
|
||||||
|
struct profile_entry_queue profiles;
|
||||||
|
|
||||||
|
static profile_t *profile_default;
|
||||||
|
|
||||||
|
static void profile_class_save ( idnode_t *in );
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
void
|
||||||
|
profile_register(const idclass_t *clazz, profile_builder_t builder)
|
||||||
|
{
|
||||||
|
profile_build_t *pb = calloc(1, sizeof(*pb));
|
||||||
|
pb->clazz = clazz;
|
||||||
|
pb->build = builder;
|
||||||
|
LIST_INSERT_HEAD(&profile_builders, pb, link);
|
||||||
|
}
|
||||||
|
|
||||||
|
static profile_build_t *
|
||||||
|
profile_class_find(const char *name)
|
||||||
|
{
|
||||||
|
profile_build_t *pb;
|
||||||
|
LIST_FOREACH(pb, &profile_builders, link) {
|
||||||
|
if (strcmp(pb->clazz->ic_class, name) == 0)
|
||||||
|
return pb;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
profile_t *
|
||||||
|
profile_create
|
||||||
|
(const char *uuid, htsmsg_t *conf, int save)
|
||||||
|
{
|
||||||
|
profile_t *pro = NULL;
|
||||||
|
profile_build_t *pb = NULL;
|
||||||
|
const char *s;
|
||||||
|
|
||||||
|
lock_assert(&global_lock);
|
||||||
|
|
||||||
|
if ((s = htsmsg_get_str(conf, "class")) != NULL)
|
||||||
|
pb = profile_class_find(s);
|
||||||
|
if (pb == NULL) {
|
||||||
|
tvherror("profile", "wrong class %s!", s);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
pro = pb->build();
|
||||||
|
if (pro == NULL) {
|
||||||
|
tvherror("profile", "Profile class %s is not available!", s);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
LIST_INIT(&pro->pro_dvr_configs);
|
||||||
|
if (idnode_insert(&pro->pro_id, uuid, pb->clazz, 0)) {
|
||||||
|
if (uuid)
|
||||||
|
tvherror("profile", "invalid uuid '%s'", uuid);
|
||||||
|
free(pro);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (conf) {
|
||||||
|
int b;
|
||||||
|
idnode_load(&pro->pro_id, conf);
|
||||||
|
if (!htsmsg_get_bool(conf, "shield", &b))
|
||||||
|
pro->pro_shield = !!b;
|
||||||
|
}
|
||||||
|
TAILQ_INSERT_TAIL(&profiles, pro, pro_link);
|
||||||
|
if (save)
|
||||||
|
profile_class_save((idnode_t *)pro);
|
||||||
|
if (pro->pro_conf_changed)
|
||||||
|
pro->pro_conf_changed(pro);
|
||||||
|
return pro;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
profile_delete(profile_t *pro, int delconf)
|
||||||
|
{
|
||||||
|
pro->pro_enabled = 0;
|
||||||
|
if (pro->pro_conf_changed)
|
||||||
|
pro->pro_conf_changed(pro);
|
||||||
|
if (delconf)
|
||||||
|
hts_settings_remove("profile/%s", idnode_uuid_as_str(&pro->pro_id));
|
||||||
|
TAILQ_REMOVE(&profiles, pro, pro_link);
|
||||||
|
idnode_unlink(&pro->pro_id);
|
||||||
|
dvr_config_destroy_by_profile(pro, delconf);
|
||||||
|
if (pro->pro_free)
|
||||||
|
pro->pro_free(pro);
|
||||||
|
free(pro->pro_name);
|
||||||
|
free(pro->pro_comment);
|
||||||
|
free(pro);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
profile_class_save ( idnode_t *in )
|
||||||
|
{
|
||||||
|
profile_t *pro = (profile_t *)in;
|
||||||
|
htsmsg_t *c = htsmsg_create_map();
|
||||||
|
idnode_save(in, c);
|
||||||
|
if (pro->pro_shield)
|
||||||
|
htsmsg_add_bool(c, "shield", 1);
|
||||||
|
hts_settings_save(c, "profile/%s", idnode_uuid_as_str(in));
|
||||||
|
htsmsg_destroy(c);
|
||||||
|
if (pro->pro_conf_changed)
|
||||||
|
pro->pro_conf_changed(pro);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *
|
||||||
|
profile_class_get_title ( idnode_t *in )
|
||||||
|
{
|
||||||
|
profile_t *pro = (profile_t *)in;
|
||||||
|
static char buf[32];
|
||||||
|
if (pro->pro_name && pro->pro_name[0])
|
||||||
|
return pro->pro_name;
|
||||||
|
snprintf(buf, sizeof(buf), "%s", in->in_class->ic_caption);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
profile_class_delete(idnode_t *self)
|
||||||
|
{
|
||||||
|
profile_t *pro = (profile_t *)self;
|
||||||
|
if (pro->pro_shield)
|
||||||
|
return;
|
||||||
|
profile_delete(pro, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const void *
|
||||||
|
profile_class_class_get(void *o)
|
||||||
|
{
|
||||||
|
profile_t *pro = o;
|
||||||
|
static const char *ret;
|
||||||
|
ret = pro->pro_id.in_class->ic_class;
|
||||||
|
return &ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
profile_class_class_set(void *o, const void *v)
|
||||||
|
{
|
||||||
|
/* just ignore, create fcn does the right job */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const void *
|
||||||
|
profile_class_default_get(void *o)
|
||||||
|
{
|
||||||
|
static int res;
|
||||||
|
res = o == profile_default;
|
||||||
|
return &res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
profile_class_default_set(void *o, const void *v)
|
||||||
|
{
|
||||||
|
profile_t *pro = o, *old;
|
||||||
|
if (*(int *)v && pro != profile_default) {
|
||||||
|
old = profile_default;
|
||||||
|
profile_default = pro;
|
||||||
|
if (old)
|
||||||
|
profile_class_save(&old->pro_id);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const idclass_t profile_class =
|
||||||
|
{
|
||||||
|
.ic_class = "profile",
|
||||||
|
.ic_caption = "Stream Profile",
|
||||||
|
.ic_save = profile_class_save,
|
||||||
|
.ic_event = "profile",
|
||||||
|
.ic_get_title = profile_class_get_title,
|
||||||
|
.ic_delete = profile_class_delete,
|
||||||
|
.ic_properties = (const property_t[]){
|
||||||
|
{
|
||||||
|
.type = PT_STR,
|
||||||
|
.id = "class",
|
||||||
|
.name = "Class",
|
||||||
|
.opts = PO_RDONLY | PO_HIDDEN,
|
||||||
|
.get = profile_class_class_get,
|
||||||
|
.set = profile_class_class_set,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.type = PT_BOOL,
|
||||||
|
.id = "enabled",
|
||||||
|
.name = "Enabled",
|
||||||
|
.off = offsetof(profile_t, pro_enabled),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.type = PT_BOOL,
|
||||||
|
.id = "default",
|
||||||
|
.name = "Default",
|
||||||
|
.set = profile_class_default_set,
|
||||||
|
.get = profile_class_default_get,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.type = PT_STR,
|
||||||
|
.id = "name",
|
||||||
|
.name = "Profile Name",
|
||||||
|
.off = offsetof(profile_t, pro_name),
|
||||||
|
.notify = idnode_notify_title_changed,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.type = PT_STR,
|
||||||
|
.id = "comment",
|
||||||
|
.name = "Comment",
|
||||||
|
.off = offsetof(profile_t, pro_comment),
|
||||||
|
},
|
||||||
|
{ }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const char *
|
||||||
|
profile_get_name(profile_t *pro)
|
||||||
|
{
|
||||||
|
if (pro->pro_name && *pro->pro_name) return pro->pro_name;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
profile_t *
|
||||||
|
profile_find_by_name(const char *name)
|
||||||
|
{
|
||||||
|
profile_t *pro;
|
||||||
|
|
||||||
|
lock_assert(&global_lock);
|
||||||
|
|
||||||
|
if (!name)
|
||||||
|
return profile_default;
|
||||||
|
|
||||||
|
TAILQ_FOREACH(pro, &profiles, pro_link) {
|
||||||
|
if (!strcmp(pro->pro_name, name))
|
||||||
|
return pro;
|
||||||
|
}
|
||||||
|
|
||||||
|
return profile_default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
profile_validate_name(const char *name)
|
||||||
|
{
|
||||||
|
profile_t *pro;
|
||||||
|
|
||||||
|
lock_assert(&global_lock);
|
||||||
|
|
||||||
|
TAILQ_FOREACH(pro, &profiles, pro_link) {
|
||||||
|
if (name && !strcmp(pro->pro_name, name))
|
||||||
|
return strdup(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (profile_default)
|
||||||
|
return strdup(profile_default->pro_name);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
htsmsg_t *
|
||||||
|
profile_class_get_list(void *o)
|
||||||
|
{
|
||||||
|
htsmsg_t *m = htsmsg_create_map();
|
||||||
|
htsmsg_add_str(m, "type", "api");
|
||||||
|
htsmsg_add_str(m, "uri", "profile/list");
|
||||||
|
htsmsg_add_str(m, "event", "profile");
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
profile_chain_raw_open(profile_chain_t *prch, size_t qsize)
|
||||||
|
{
|
||||||
|
muxer_config_t c;
|
||||||
|
|
||||||
|
memset(&c, 0, sizeof(c));
|
||||||
|
c.m_type = MC_RAW;
|
||||||
|
memset(prch, 0, sizeof(*prch));
|
||||||
|
prch->prch_flags = SUBSCRIPTION_RAW_MPEGTS;
|
||||||
|
streaming_queue_init(&prch->prch_sq, SMT_PACKET, qsize);
|
||||||
|
prch->prch_st = &prch->prch_sq.sq_st;
|
||||||
|
prch->prch_muxer = muxer_create(&c);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
profile_chain_close(profile_chain_t *prch)
|
||||||
|
{
|
||||||
|
if (prch->prch_tsfix)
|
||||||
|
tsfix_destroy(prch->prch_tsfix);
|
||||||
|
if (prch->prch_gh)
|
||||||
|
globalheaders_destroy(prch->prch_gh);
|
||||||
|
if (prch->prch_muxer)
|
||||||
|
muxer_destroy(prch->prch_muxer);
|
||||||
|
streaming_queue_deinit(&prch->prch_sq);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* MPEG-TS passthrough muxer
|
||||||
|
*/
|
||||||
|
typedef struct profile_mpegts {
|
||||||
|
profile_t;
|
||||||
|
int pro_rewrite_pmt;
|
||||||
|
int pro_rewrite_pat;
|
||||||
|
} profile_mpegts_t;
|
||||||
|
|
||||||
|
const idclass_t profile_mpegts_pass_class =
|
||||||
|
{
|
||||||
|
.ic_super = &profile_class,
|
||||||
|
.ic_class = "profile-mpegts",
|
||||||
|
.ic_caption = "MPEG-TS Pass-through",
|
||||||
|
.ic_properties = (const property_t[]){
|
||||||
|
{
|
||||||
|
.type = PT_BOOL,
|
||||||
|
.id = "rewrite_pmt",
|
||||||
|
.name = "Rewrite PMT",
|
||||||
|
.off = offsetof(profile_mpegts_t, pro_rewrite_pmt),
|
||||||
|
.def.i = 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.type = PT_BOOL,
|
||||||
|
.id = "rewrite_pat",
|
||||||
|
.name = "Rewrite PAT",
|
||||||
|
.off = offsetof(profile_mpegts_t, pro_rewrite_pat),
|
||||||
|
.def.i = 1,
|
||||||
|
},
|
||||||
|
{ }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static int
|
||||||
|
profile_mpegts_pass_open(profile_t *_pro, profile_chain_t *prch,
|
||||||
|
muxer_config_t *m_cfg, size_t qsize)
|
||||||
|
{
|
||||||
|
profile_mpegts_t *pro = (profile_mpegts_t *)_pro;
|
||||||
|
muxer_config_t c;
|
||||||
|
|
||||||
|
if (m_cfg)
|
||||||
|
c = *m_cfg; /* do not alter the original parameter */
|
||||||
|
else
|
||||||
|
memset(&c, 0, sizeof(c));
|
||||||
|
if (c.m_type != MC_RAW)
|
||||||
|
c.m_type = MC_PASS;
|
||||||
|
c.m_rewrite_pat = pro->pro_rewrite_pat;
|
||||||
|
c.m_rewrite_pmt = pro->pro_rewrite_pmt;
|
||||||
|
|
||||||
|
memset(prch, 0, sizeof(*prch));
|
||||||
|
prch->prch_flags = SUBSCRIPTION_RAW_MPEGTS;
|
||||||
|
streaming_queue_init(&prch->prch_sq, SMT_PACKET, qsize);
|
||||||
|
prch->prch_muxer = muxer_create(&c);
|
||||||
|
prch->prch_st = &prch->prch_sq.sq_st;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static muxer_container_type_t
|
||||||
|
profile_mpegts_pass_get_mc(profile_t *_pro)
|
||||||
|
{
|
||||||
|
return MC_PASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static profile_t *
|
||||||
|
profile_mpegts_pass_builder(void)
|
||||||
|
{
|
||||||
|
profile_mpegts_t *pro = calloc(1, sizeof(*pro));
|
||||||
|
pro->pro_open = profile_mpegts_pass_open;
|
||||||
|
pro->pro_get_mc = profile_mpegts_pass_get_mc;
|
||||||
|
return (profile_t *)pro;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Matroska muxer
|
||||||
|
*/
|
||||||
|
typedef struct profile_matroska {
|
||||||
|
profile_t;
|
||||||
|
int pro_webm;
|
||||||
|
} profile_matroska_t;
|
||||||
|
|
||||||
|
const idclass_t profile_matroska_class =
|
||||||
|
{
|
||||||
|
.ic_super = &profile_class,
|
||||||
|
.ic_class = "profile-matroska",
|
||||||
|
.ic_caption = "Matroska (mkv)",
|
||||||
|
.ic_properties = (const property_t[]){
|
||||||
|
{
|
||||||
|
.type = PT_BOOL,
|
||||||
|
.id = "webm",
|
||||||
|
.name = "WEBM",
|
||||||
|
.off = offsetof(profile_matroska_t, pro_webm),
|
||||||
|
.def.i = 0,
|
||||||
|
},
|
||||||
|
{ }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static int
|
||||||
|
profile_matroska_open(profile_t *_pro, profile_chain_t *prch,
|
||||||
|
muxer_config_t *m_cfg, size_t qsize)
|
||||||
|
{
|
||||||
|
profile_matroska_t *pro = (profile_matroska_t *)_pro;
|
||||||
|
muxer_config_t c;
|
||||||
|
|
||||||
|
if (m_cfg)
|
||||||
|
c = *m_cfg; /* do not alter the original parameter */
|
||||||
|
else
|
||||||
|
memset(&c, 0, sizeof(c));
|
||||||
|
if (c.m_type != MC_WEBM)
|
||||||
|
c.m_type = MC_MATROSKA;
|
||||||
|
if (pro->pro_webm)
|
||||||
|
c.m_type = MC_WEBM;
|
||||||
|
|
||||||
|
memset(prch, 0, sizeof(*prch));
|
||||||
|
streaming_queue_init(&prch->prch_sq, 0, qsize);
|
||||||
|
prch->prch_gh = globalheaders_create(&prch->prch_sq.sq_st);
|
||||||
|
prch->prch_tsfix = tsfix_create(prch->prch_gh);
|
||||||
|
prch->prch_muxer = muxer_create(&c);
|
||||||
|
prch->prch_st = prch->prch_tsfix;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static muxer_container_type_t
|
||||||
|
profile_matroska_get_mc(profile_t *_pro)
|
||||||
|
{
|
||||||
|
profile_matroska_t *pro = (profile_matroska_t *)_pro;
|
||||||
|
if (pro->pro_webm)
|
||||||
|
return MC_WEBM;
|
||||||
|
return MC_MATROSKA;
|
||||||
|
}
|
||||||
|
|
||||||
|
static profile_t *
|
||||||
|
profile_matroska_builder(void)
|
||||||
|
{
|
||||||
|
profile_matroska_t *pro = calloc(1, sizeof(*pro));
|
||||||
|
pro->pro_open = profile_matroska_open;
|
||||||
|
pro->pro_get_mc = profile_matroska_get_mc;
|
||||||
|
return (profile_t *)pro;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initialize
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
profile_init(void)
|
||||||
|
{
|
||||||
|
htsmsg_t *c, *e;
|
||||||
|
htsmsg_field_t *f;
|
||||||
|
|
||||||
|
LIST_INIT(&profile_builders);
|
||||||
|
TAILQ_INIT(&profiles);
|
||||||
|
|
||||||
|
profile_register(&profile_mpegts_pass_class, profile_mpegts_pass_builder);
|
||||||
|
profile_register(&profile_matroska_class, profile_matroska_builder);
|
||||||
|
|
||||||
|
if ((c = hts_settings_load("profile")) != NULL) {
|
||||||
|
HTSMSG_FOREACH(f, c) {
|
||||||
|
if (!(e = htsmsg_field_get_map(f)))
|
||||||
|
continue;
|
||||||
|
(void)profile_create(f->hmf_name, e, 0);
|
||||||
|
}
|
||||||
|
htsmsg_destroy(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TAILQ_EMPTY(&profiles)) {
|
||||||
|
htsmsg_t *conf;
|
||||||
|
|
||||||
|
conf = htsmsg_create_map();
|
||||||
|
htsmsg_add_str (conf, "class", "profile-mpegts");
|
||||||
|
htsmsg_add_bool(conf, "enabled", 1);
|
||||||
|
htsmsg_add_bool(conf, "default", 1);
|
||||||
|
htsmsg_add_str (conf, "name", "pass");
|
||||||
|
htsmsg_add_str (conf, "comment", "MPEG-TS Pass-through");
|
||||||
|
htsmsg_add_bool(conf, "rewrite_pmt", 1);
|
||||||
|
htsmsg_add_bool(conf, "rewrite_pat", 1);
|
||||||
|
htsmsg_add_bool(conf, "shield", 1);
|
||||||
|
(void)profile_create(NULL, conf, 1);
|
||||||
|
htsmsg_destroy(conf);
|
||||||
|
|
||||||
|
conf = htsmsg_create_map();
|
||||||
|
htsmsg_add_str (conf, "class", "profile-matroska");
|
||||||
|
htsmsg_add_bool(conf, "enabled", 1);
|
||||||
|
htsmsg_add_str (conf, "name", "matroska");
|
||||||
|
htsmsg_add_str (conf, "comment", "Matroska");
|
||||||
|
htsmsg_add_bool(conf, "shield", 1);
|
||||||
|
(void)profile_create(NULL, conf, 1);
|
||||||
|
htsmsg_destroy(conf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
profile_done(void)
|
||||||
|
{
|
||||||
|
profile_t *pro;
|
||||||
|
profile_build_t *pb;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&global_lock);
|
||||||
|
profile_default = NULL;
|
||||||
|
while ((pro = TAILQ_FIRST(&profiles)) != NULL)
|
||||||
|
profile_delete(pro, 0);
|
||||||
|
while ((pb = LIST_FIRST(&profile_builders)) != NULL) {
|
||||||
|
LIST_REMOVE(pb, link);
|
||||||
|
free(pb);
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&global_lock);
|
||||||
|
}
|
102
src/profile.h
Normal file
102
src/profile.h
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
/*
|
||||||
|
* tvheadend, Stream Profile
|
||||||
|
* Copyright (C) 2014 Jaroslav Kysela
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __TVH_PROFILE_H__
|
||||||
|
#define __TVH_PROFILE_H__
|
||||||
|
|
||||||
|
#include "tvheadend.h"
|
||||||
|
#include "idnode.h"
|
||||||
|
#include "muxer.h"
|
||||||
|
|
||||||
|
struct profile;
|
||||||
|
struct muxer;
|
||||||
|
struct streaming_target;
|
||||||
|
|
||||||
|
extern const idclass_t profile_class;
|
||||||
|
extern const idclass_t profile_mpegts_pass_class;
|
||||||
|
extern const idclass_t profile_matroska_class;
|
||||||
|
|
||||||
|
TAILQ_HEAD(profile_entry_queue, profile);
|
||||||
|
|
||||||
|
extern struct profile_entry_queue profiles;
|
||||||
|
|
||||||
|
typedef struct profile *(*profile_builder_t)(void);
|
||||||
|
|
||||||
|
typedef struct profile_build {
|
||||||
|
LIST_ENTRY(profile_build) link;
|
||||||
|
const idclass_t *clazz;
|
||||||
|
profile_builder_t build;
|
||||||
|
} profile_build_t;
|
||||||
|
|
||||||
|
typedef LIST_HEAD(, profile_build) profile_builders_queue;
|
||||||
|
|
||||||
|
extern profile_builders_queue profile_builders;
|
||||||
|
|
||||||
|
typedef struct profile_chain {
|
||||||
|
int prch_flags;
|
||||||
|
struct streaming_queue prch_sq;
|
||||||
|
struct streaming_target *prch_st;
|
||||||
|
struct muxer *prch_muxer;
|
||||||
|
struct streaming_target *prch_gh;
|
||||||
|
struct streaming_target *prch_tsfix;
|
||||||
|
} profile_chain_t;
|
||||||
|
|
||||||
|
typedef struct profile {
|
||||||
|
idnode_t pro_id;
|
||||||
|
TAILQ_ENTRY(profile) pro_link;
|
||||||
|
|
||||||
|
LIST_HEAD(,dvr_config) pro_dvr_configs;
|
||||||
|
|
||||||
|
int pro_enabled;
|
||||||
|
int pro_shield;
|
||||||
|
char *pro_name;
|
||||||
|
char *pro_comment;
|
||||||
|
|
||||||
|
void (*pro_free)(struct profile *cac);
|
||||||
|
void (*pro_conf_changed)(struct profile *cac);
|
||||||
|
muxer_container_type_t (*pro_get_mc)(struct profile *cac);
|
||||||
|
|
||||||
|
int (*pro_open)(struct profile *cac, profile_chain_t *prch,
|
||||||
|
muxer_config_t *m_cfg, size_t qsize);
|
||||||
|
} profile_t;
|
||||||
|
|
||||||
|
void profile_register(const idclass_t *clazz, profile_builder_t builder);
|
||||||
|
|
||||||
|
profile_t *profile_create
|
||||||
|
(const char *uuid, htsmsg_t *conf, int save);
|
||||||
|
|
||||||
|
int profile_chain_raw_open(profile_chain_t *prch, size_t qsize);
|
||||||
|
void profile_chain_close(profile_chain_t *prch);
|
||||||
|
|
||||||
|
static inline profile_t *profile_find_by_uuid(const char *uuid)
|
||||||
|
{ return (profile_t*)idnode_find(uuid, &profile_class, NULL); }
|
||||||
|
profile_t *profile_find_by_name(const char *name);
|
||||||
|
|
||||||
|
htsmsg_t * profile_class_get_list(void *o);
|
||||||
|
|
||||||
|
char *profile_validate_name(const char *name);
|
||||||
|
|
||||||
|
const char *profile_get_name(profile_t *pro);
|
||||||
|
|
||||||
|
static inline muxer_container_type_t profile_get_mc(profile_t *pro)
|
||||||
|
{ return pro->pro_get_mc(pro); }
|
||||||
|
|
||||||
|
void profile_init(void);
|
||||||
|
void profile_done(void);
|
||||||
|
|
||||||
|
#endif /* __TVH_PROFILE_H__ */
|
|
@ -334,7 +334,7 @@ service_mapper_thread ( void *aux )
|
||||||
streaming_message_t *sm;
|
streaming_message_t *sm;
|
||||||
const char *err = NULL;
|
const char *err = NULL;
|
||||||
|
|
||||||
streaming_queue_init(&sq, 0);
|
streaming_queue_init(&sq, 0, 0);
|
||||||
|
|
||||||
pthread_mutex_lock(&global_lock);
|
pthread_mutex_lock(&global_lock);
|
||||||
|
|
||||||
|
|
|
@ -75,7 +75,7 @@ streaming_queue_deliver(void *opauqe, streaming_message_t *sm)
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
streaming_queue_init2(streaming_queue_t *sq, int reject_filter, size_t maxsize)
|
streaming_queue_init(streaming_queue_t *sq, int reject_filter, size_t maxsize)
|
||||||
{
|
{
|
||||||
streaming_target_init(&sq->sq_st, streaming_queue_deliver, sq, reject_filter);
|
streaming_target_init(&sq->sq_st, streaming_queue_deliver, sq, reject_filter);
|
||||||
|
|
||||||
|
@ -86,16 +86,6 @@ streaming_queue_init2(streaming_queue_t *sq, int reject_filter, size_t maxsize)
|
||||||
sq->sq_maxsize = maxsize;
|
sq->sq_maxsize = maxsize;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
streaming_queue_init(streaming_queue_t *sq, int reject_filter)
|
|
||||||
{
|
|
||||||
streaming_queue_init2(sq, reject_filter, 0); // 0 = unlimited
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -72,9 +72,7 @@ void streaming_target_init(streaming_target_t *st,
|
||||||
st_callback_t *cb, void *opaque,
|
st_callback_t *cb, void *opaque,
|
||||||
int reject_filter);
|
int reject_filter);
|
||||||
|
|
||||||
void streaming_queue_init(streaming_queue_t *sq, int reject_filter);
|
void streaming_queue_init
|
||||||
|
|
||||||
void streaming_queue_init2
|
|
||||||
(streaming_queue_t *sq, int reject_filter, size_t maxsize);
|
(streaming_queue_t *sq, int reject_filter, size_t maxsize);
|
||||||
|
|
||||||
void streaming_queue_clear(struct streaming_message_queue *q);
|
void streaming_queue_clear(struct streaming_message_queue *q);
|
||||||
|
|
|
@ -278,7 +278,7 @@ streaming_target_t *timeshift_create
|
||||||
tvh_pipe(O_NONBLOCK, &ts->rd_pipe);
|
tvh_pipe(O_NONBLOCK, &ts->rd_pipe);
|
||||||
|
|
||||||
/* Initialise input */
|
/* Initialise input */
|
||||||
streaming_queue_init(&ts->wr_queue, 0);
|
streaming_queue_init(&ts->wr_queue, 0, 0);
|
||||||
streaming_target_init(&ts->input, timeshift_input, ts, 0);
|
streaming_target_init(&ts->input, timeshift_input, ts, 0);
|
||||||
tvhthread_create(&ts->wr_thread, NULL, timeshift_writer, ts);
|
tvhthread_create(&ts->wr_thread, NULL, timeshift_writer, ts);
|
||||||
tvhthread_create(&ts->rd_thread, NULL, timeshift_reader, ts);
|
tvhthread_create(&ts->rd_thread, NULL, timeshift_reader, ts);
|
||||||
|
|
|
@ -1,14 +1,47 @@
|
||||||
/*
|
/*
|
||||||
* Elementary Stream Filters
|
* Stream Profiles, Elementary Stream Filters
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
tvheadend.caclient_builders = new Ext.data.JsonStore({
|
||||||
|
url: 'api/profile/builders',
|
||||||
|
root: 'entries',
|
||||||
|
fields: ['class', 'caption', 'props'],
|
||||||
|
id: 'class',
|
||||||
|
autoLoad: true
|
||||||
|
});
|
||||||
|
|
||||||
tvheadend.esfilter_tab = function(panel)
|
tvheadend.esfilter_tab = function(panel)
|
||||||
{
|
{
|
||||||
|
tvheadend.idnode_form_grid(panel, {
|
||||||
|
url: 'api/profile',
|
||||||
|
clazz: 'profile',
|
||||||
|
comet: 'profile',
|
||||||
|
titleS: 'Stream Profile',
|
||||||
|
titleC: 'Stream Profile Name',
|
||||||
|
iconCls: 'stream_profile',
|
||||||
|
add: {
|
||||||
|
url: 'api/profile',
|
||||||
|
titleS: 'Stream Profile',
|
||||||
|
select: {
|
||||||
|
label: 'Type',
|
||||||
|
store: tvheadend.caclient_builders,
|
||||||
|
displayField: 'caption',
|
||||||
|
valueField: 'class',
|
||||||
|
propField: 'props'
|
||||||
|
},
|
||||||
|
create: { },
|
||||||
|
},
|
||||||
|
del: true,
|
||||||
|
help: function() {
|
||||||
|
new tvheadend.help('Stream Profile', 'config_profile.html');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
tvheadend.idnode_grid(panel, {
|
tvheadend.idnode_grid(panel, {
|
||||||
url: 'api/esfilter/video',
|
url: 'api/esfilter/video',
|
||||||
titleS: 'Video Stream Filter',
|
titleS: 'Video Stream Filter',
|
||||||
titleP: 'Video Stream Filters',
|
titleP: 'Video Stream Filters',
|
||||||
tabIndex: 0,
|
tabIndex: 1,
|
||||||
add: {
|
add: {
|
||||||
url: 'api/esfilter/video',
|
url: 'api/esfilter/video',
|
||||||
create: {}
|
create: {}
|
||||||
|
@ -24,7 +57,7 @@ tvheadend.esfilter_tab = function(panel)
|
||||||
url: 'api/esfilter/audio',
|
url: 'api/esfilter/audio',
|
||||||
titleS: 'Audio Stream Filter',
|
titleS: 'Audio Stream Filter',
|
||||||
titleP: 'Audio Stream Filters',
|
titleP: 'Audio Stream Filters',
|
||||||
tabIndex: 1,
|
tabIndex: 2,
|
||||||
add: {
|
add: {
|
||||||
url: 'api/esfilter/audio',
|
url: 'api/esfilter/audio',
|
||||||
create: {}
|
create: {}
|
||||||
|
@ -40,7 +73,7 @@ tvheadend.esfilter_tab = function(panel)
|
||||||
url: 'api/esfilter/teletext',
|
url: 'api/esfilter/teletext',
|
||||||
titleS: 'Teletext Stream Filter',
|
titleS: 'Teletext Stream Filter',
|
||||||
titleP: 'Teletext Stream Filters',
|
titleP: 'Teletext Stream Filters',
|
||||||
tabIndex: 2,
|
tabIndex: 3,
|
||||||
add: {
|
add: {
|
||||||
url: 'api/esfilter/teletext',
|
url: 'api/esfilter/teletext',
|
||||||
create: {}
|
create: {}
|
||||||
|
@ -56,7 +89,7 @@ tvheadend.esfilter_tab = function(panel)
|
||||||
url: 'api/esfilter/subtit',
|
url: 'api/esfilter/subtit',
|
||||||
titleS: 'Subtitle Stream Filter',
|
titleS: 'Subtitle Stream Filter',
|
||||||
titleP: 'Subtitle Stream Filters',
|
titleP: 'Subtitle Stream Filters',
|
||||||
tabIndex: 3,
|
tabIndex: 4,
|
||||||
add: {
|
add: {
|
||||||
url: 'api/esfilter/subtit',
|
url: 'api/esfilter/subtit',
|
||||||
create: {}
|
create: {}
|
||||||
|
@ -72,7 +105,7 @@ tvheadend.esfilter_tab = function(panel)
|
||||||
url: 'api/esfilter/ca',
|
url: 'api/esfilter/ca',
|
||||||
titleS: 'CA Stream Filter',
|
titleS: 'CA Stream Filter',
|
||||||
titleP: 'CA Stream Filters',
|
titleP: 'CA Stream Filters',
|
||||||
tabIndex: 4,
|
tabIndex: 5,
|
||||||
add: {
|
add: {
|
||||||
url: 'api/esfilter/ca',
|
url: 'api/esfilter/ca',
|
||||||
create: {}
|
create: {}
|
||||||
|
@ -88,7 +121,7 @@ tvheadend.esfilter_tab = function(panel)
|
||||||
url: 'api/esfilter/other',
|
url: 'api/esfilter/other',
|
||||||
titleS: 'Other Stream Filter',
|
titleS: 'Other Stream Filter',
|
||||||
titleP: 'Other Stream Filters',
|
titleP: 'Other Stream Filters',
|
||||||
tabIndex: 5,
|
tabIndex: 6,
|
||||||
add: {
|
add: {
|
||||||
url: 'api/esfilter/other',
|
url: 'api/esfilter/other',
|
||||||
create: {}
|
create: {}
|
||||||
|
|
|
@ -378,6 +378,18 @@ function accessUpdate(o) {
|
||||||
|
|
||||||
cp.add(chepg);
|
cp.add(chepg);
|
||||||
|
|
||||||
|
/* Stream Config */
|
||||||
|
var stream = new Ext.TabPanel({
|
||||||
|
activeTab: 0,
|
||||||
|
autoScroll: true,
|
||||||
|
title: 'Stream',
|
||||||
|
iconCls: 'stream_config',
|
||||||
|
items: []
|
||||||
|
});
|
||||||
|
tvheadend.esfilter_tab(stream);
|
||||||
|
|
||||||
|
cp.add(stream);
|
||||||
|
|
||||||
/* DVR / Timeshift */
|
/* DVR / Timeshift */
|
||||||
var tsdvr = new Ext.TabPanel({
|
var tsdvr = new Ext.TabPanel({
|
||||||
activeTab: 0,
|
activeTab: 0,
|
||||||
|
@ -396,18 +408,6 @@ function accessUpdate(o) {
|
||||||
if (tvheadend.capabilities.indexOf('caclient') !== -1)
|
if (tvheadend.capabilities.indexOf('caclient') !== -1)
|
||||||
tvheadend.caclient(cp, null);
|
tvheadend.caclient(cp, null);
|
||||||
|
|
||||||
/* Stream Config */
|
|
||||||
var stream = new Ext.TabPanel({
|
|
||||||
activeTab: 0,
|
|
||||||
autoScroll: true,
|
|
||||||
title: 'Stream',
|
|
||||||
iconCls: 'stream_config',
|
|
||||||
items: []
|
|
||||||
});
|
|
||||||
tvheadend.esfilter_tab(stream);
|
|
||||||
|
|
||||||
cp.add(stream);
|
|
||||||
|
|
||||||
/* Debug */
|
/* Debug */
|
||||||
tvheadend.tvhlog(cp);
|
tvheadend.tvhlog(cp);
|
||||||
|
|
||||||
|
|
|
@ -37,15 +37,17 @@
|
||||||
#include "dvr/dvr.h"
|
#include "dvr/dvr.h"
|
||||||
#include "filebundle.h"
|
#include "filebundle.h"
|
||||||
#include "streaming.h"
|
#include "streaming.h"
|
||||||
#include "plumbing/tsfix.h"
|
|
||||||
#include "plumbing/globalheaders.h"
|
|
||||||
#include "plumbing/transcoding.h"
|
#include "plumbing/transcoding.h"
|
||||||
|
#include "profile.h"
|
||||||
#include "epg.h"
|
#include "epg.h"
|
||||||
#include "muxer.h"
|
#include "muxer.h"
|
||||||
#include "imagecache.h"
|
#include "imagecache.h"
|
||||||
#include "tcp.h"
|
#include "tcp.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "atomic.h"
|
#include "atomic.h"
|
||||||
|
#if ENABLE_MPEGTS
|
||||||
|
#include "input.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(PLATFORM_LINUX)
|
#if defined(PLATFORM_LINUX)
|
||||||
#include <sys/sendfile.h>
|
#include <sys/sendfile.h>
|
||||||
|
@ -287,21 +289,20 @@ http_stream_postop ( void *tcp_id )
|
||||||
* HTTP stream loop
|
* HTTP stream loop
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
http_stream_run(http_connection_t *hc, streaming_queue_t *sq,
|
http_stream_run(http_connection_t *hc, profile_chain_t *prch,
|
||||||
const char *name, muxer_container_type_t mc,
|
const char *name, th_subscription_t *s)
|
||||||
th_subscription_t *s, muxer_config_t *mcfg)
|
|
||||||
{
|
{
|
||||||
streaming_message_t *sm;
|
streaming_message_t *sm;
|
||||||
int run = 1;
|
int run = 1;
|
||||||
int started = 0;
|
int started = 0;
|
||||||
muxer_t *mux = NULL;
|
streaming_queue_t *sq = &prch->prch_sq;
|
||||||
|
muxer_t *mux = prch->prch_muxer;
|
||||||
int timeouts = 0, grace = 20;
|
int timeouts = 0, grace = 20;
|
||||||
struct timespec ts;
|
struct timespec ts;
|
||||||
struct timeval tp;
|
struct timeval tp;
|
||||||
int err = 0;
|
int err = 0;
|
||||||
socklen_t errlen = sizeof(err);
|
socklen_t errlen = sizeof(err);
|
||||||
|
|
||||||
mux = muxer_create(mc, mcfg);
|
|
||||||
if(muxer_open_stream(mux, hc->hc_fd))
|
if(muxer_open_stream(mux, hc->hc_fd))
|
||||||
run = 0;
|
run = 0;
|
||||||
|
|
||||||
|
@ -419,8 +420,6 @@ http_stream_run(http_connection_t *hc, streaming_queue_t *sq,
|
||||||
|
|
||||||
if(started)
|
if(started)
|
||||||
muxer_close(mux);
|
muxer_close(mux);
|
||||||
|
|
||||||
muxer_destroy(mux);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -433,14 +432,12 @@ http_channel_playlist(http_connection_t *hc, channel_t *channel)
|
||||||
htsbuf_queue_t *hq;
|
htsbuf_queue_t *hq;
|
||||||
char buf[255];
|
char buf[255];
|
||||||
const char *host;
|
const char *host;
|
||||||
muxer_container_type_t mc;
|
char *profile;
|
||||||
|
|
||||||
if (http_access_verify_channel(hc, ACCESS_STREAMING, channel, 1))
|
if (http_access_verify_channel(hc, ACCESS_STREAMING, channel, 1))
|
||||||
return HTTP_STATUS_UNAUTHORIZED;
|
return HTTP_STATUS_UNAUTHORIZED;
|
||||||
|
|
||||||
mc = muxer_container_txt2type(http_arg_get(&hc->hc_req_args, "mux"));
|
profile = profile_validate_name(http_arg_get(&hc->hc_req_args, "profile"));
|
||||||
if(mc == MC_UNKNOWN)
|
|
||||||
mc = dvr_config_find_by_name_default(NULL)->dvr_mc;
|
|
||||||
|
|
||||||
hq = &hc->hc_reply;
|
hq = &hc->hc_reply;
|
||||||
host = http_arg_get(&hc->hc_args, "Host");
|
host = http_arg_get(&hc->hc_args, "Host");
|
||||||
|
@ -472,10 +469,11 @@ http_channel_playlist(http_connection_t *hc, channel_t *channel)
|
||||||
htsbuf_qprintf(hq, "&scodec=%s", streaming_component_type2txt(props.tp_scodec));
|
htsbuf_qprintf(hq, "&scodec=%s", streaming_component_type2txt(props.tp_scodec));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
htsbuf_qprintf(hq, "&mux=%s\n", muxer_container_type2txt(mc));
|
htsbuf_qprintf(hq, "&profile=%s\n", profile);
|
||||||
|
|
||||||
http_output_content(hc, "audio/x-mpegurl");
|
http_output_content(hc, "audio/x-mpegurl");
|
||||||
|
|
||||||
|
free(profile);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -490,14 +488,12 @@ http_tag_playlist(http_connection_t *hc, channel_tag_t *tag)
|
||||||
char buf[255];
|
char buf[255];
|
||||||
channel_tag_mapping_t *ctm;
|
channel_tag_mapping_t *ctm;
|
||||||
const char *host;
|
const char *host;
|
||||||
muxer_container_type_t mc;
|
char *profile;
|
||||||
|
|
||||||
hq = &hc->hc_reply;
|
hq = &hc->hc_reply;
|
||||||
host = http_arg_get(&hc->hc_args, "Host");
|
host = http_arg_get(&hc->hc_args, "Host");
|
||||||
|
|
||||||
mc = muxer_container_txt2type(http_arg_get(&hc->hc_req_args, "mux"));
|
profile = profile_validate_name(http_arg_get(&hc->hc_req_args, "profile"));
|
||||||
if(mc == MC_UNKNOWN)
|
|
||||||
mc = dvr_config_find_by_name_default(NULL)->dvr_mc;
|
|
||||||
|
|
||||||
htsbuf_qprintf(hq, "#EXTM3U\n");
|
htsbuf_qprintf(hq, "#EXTM3U\n");
|
||||||
LIST_FOREACH(ctm, &tag->ct_ctms, ctm_tag_link) {
|
LIST_FOREACH(ctm, &tag->ct_ctms, ctm_tag_link) {
|
||||||
|
@ -507,11 +503,12 @@ http_tag_playlist(http_connection_t *hc, channel_tag_t *tag)
|
||||||
htsbuf_qprintf(hq, "#EXTINF:-1,%s\n", channel_get_name(ctm->ctm_channel));
|
htsbuf_qprintf(hq, "#EXTINF:-1,%s\n", channel_get_name(ctm->ctm_channel));
|
||||||
htsbuf_qprintf(hq, "http://%s%s?ticket=%s", host, buf,
|
htsbuf_qprintf(hq, "http://%s%s?ticket=%s", host, buf,
|
||||||
access_ticket_create(buf, hc->hc_access));
|
access_ticket_create(buf, hc->hc_access));
|
||||||
htsbuf_qprintf(hq, "&mux=%s\n", muxer_container_type2txt(mc));
|
htsbuf_qprintf(hq, "&profile=%s\n", profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
http_output_content(hc, "audio/x-mpegurl");
|
http_output_content(hc, "audio/x-mpegurl");
|
||||||
|
|
||||||
|
free(profile);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -526,14 +523,12 @@ http_tag_list_playlist(http_connection_t *hc)
|
||||||
char buf[255];
|
char buf[255];
|
||||||
channel_tag_t *ct;
|
channel_tag_t *ct;
|
||||||
const char *host;
|
const char *host;
|
||||||
muxer_container_type_t mc;
|
char *profile;
|
||||||
|
|
||||||
hq = &hc->hc_reply;
|
hq = &hc->hc_reply;
|
||||||
host = http_arg_get(&hc->hc_args, "Host");
|
host = http_arg_get(&hc->hc_args, "Host");
|
||||||
|
|
||||||
mc = muxer_container_txt2type(http_arg_get(&hc->hc_req_args, "mux"));
|
profile = profile_validate_name(http_arg_get(&hc->hc_req_args, "profile"));
|
||||||
if(mc == MC_UNKNOWN)
|
|
||||||
mc = dvr_config_find_by_name_default(NULL)->dvr_mc;
|
|
||||||
|
|
||||||
htsbuf_qprintf(hq, "#EXTM3U\n");
|
htsbuf_qprintf(hq, "#EXTM3U\n");
|
||||||
TAILQ_FOREACH(ct, &channel_tags, ct_link) {
|
TAILQ_FOREACH(ct, &channel_tags, ct_link) {
|
||||||
|
@ -544,11 +539,12 @@ http_tag_list_playlist(http_connection_t *hc)
|
||||||
htsbuf_qprintf(hq, "#EXTINF:-1,%s\n", ct->ct_name);
|
htsbuf_qprintf(hq, "#EXTINF:-1,%s\n", ct->ct_name);
|
||||||
htsbuf_qprintf(hq, "http://%s%s?ticket=%s", host, buf,
|
htsbuf_qprintf(hq, "http://%s%s?ticket=%s", host, buf,
|
||||||
access_ticket_create(buf, hc->hc_access));
|
access_ticket_create(buf, hc->hc_access));
|
||||||
htsbuf_qprintf(hq, "&mux=%s\n", muxer_container_type2txt(mc));
|
htsbuf_qprintf(hq, "&profile=%s\n", profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
http_output_content(hc, "audio/x-mpegurl");
|
http_output_content(hc, "audio/x-mpegurl");
|
||||||
|
|
||||||
|
free(profile);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -575,14 +571,12 @@ http_channel_list_playlist(http_connection_t *hc)
|
||||||
channel_t **chlist;
|
channel_t **chlist;
|
||||||
const char *host;
|
const char *host;
|
||||||
int idx = 0, count = 0;
|
int idx = 0, count = 0;
|
||||||
muxer_container_type_t mc;
|
char *profile;
|
||||||
|
|
||||||
hq = &hc->hc_reply;
|
hq = &hc->hc_reply;
|
||||||
host = http_arg_get(&hc->hc_args, "Host");
|
host = http_arg_get(&hc->hc_args, "Host");
|
||||||
|
|
||||||
mc = muxer_container_txt2type(http_arg_get(&hc->hc_req_args, "mux"));
|
profile = profile_validate_name(http_arg_get(&hc->hc_req_args, "profile"));
|
||||||
if(mc == MC_UNKNOWN)
|
|
||||||
mc = dvr_config_find_by_name_default(NULL)->dvr_mc;
|
|
||||||
|
|
||||||
CHANNEL_FOREACH(ch)
|
CHANNEL_FOREACH(ch)
|
||||||
count++;
|
count++;
|
||||||
|
@ -608,13 +602,14 @@ http_channel_list_playlist(http_connection_t *hc)
|
||||||
htsbuf_qprintf(hq, "#EXTINF:-1,%s\n", channel_get_name(ch));
|
htsbuf_qprintf(hq, "#EXTINF:-1,%s\n", channel_get_name(ch));
|
||||||
htsbuf_qprintf(hq, "http://%s%s?ticket=%s", host, buf,
|
htsbuf_qprintf(hq, "http://%s%s?ticket=%s", host, buf,
|
||||||
access_ticket_create(buf, hc->hc_access));
|
access_ticket_create(buf, hc->hc_access));
|
||||||
htsbuf_qprintf(hq, "&mux=%s\n", muxer_container_type2txt(mc));
|
htsbuf_qprintf(hq, "&profile=%s\n", profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
free(chlist);
|
free(chlist);
|
||||||
|
|
||||||
http_output_content(hc, "audio/x-mpegurl");
|
http_output_content(hc, "audio/x-mpegurl");
|
||||||
|
|
||||||
|
free(profile);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -789,77 +784,51 @@ page_http_playlist(http_connection_t *hc, const char *remain, void *opaque)
|
||||||
static int
|
static int
|
||||||
http_stream_service(http_connection_t *hc, service_t *service, int weight)
|
http_stream_service(http_connection_t *hc, service_t *service, int weight)
|
||||||
{
|
{
|
||||||
streaming_queue_t sq;
|
|
||||||
th_subscription_t *s;
|
th_subscription_t *s;
|
||||||
streaming_target_t *gh;
|
profile_t *pro;
|
||||||
streaming_target_t *tsfix;
|
profile_chain_t prch;
|
||||||
streaming_target_t *st;
|
|
||||||
dvr_config_t *cfg;
|
|
||||||
muxer_container_type_t mc;
|
|
||||||
int flags = SUBSCRIPTION_STREAMING;
|
|
||||||
const char *str;
|
const char *str;
|
||||||
size_t qsize;
|
size_t qsize;
|
||||||
const char *name;
|
const char *name;
|
||||||
char addrbuf[50];
|
char addrbuf[50];
|
||||||
void *tcp_id;
|
void *tcp_id;
|
||||||
int res = 0;
|
int res = HTTP_STATUS_BAD_REQUEST;
|
||||||
|
|
||||||
if(http_access_verify(hc, ACCESS_ADVANCED_STREAMING))
|
if(http_access_verify(hc, ACCESS_ADVANCED_STREAMING))
|
||||||
return HTTP_STATUS_UNAUTHORIZED;
|
return HTTP_STATUS_UNAUTHORIZED;
|
||||||
|
|
||||||
if((tcp_id = http_stream_preop(hc)) == NULL)
|
if(!(pro = profile_find_by_name(http_arg_get(&hc->hc_req_args, "profile"))))
|
||||||
return HTTP_STATUS_NOT_ALLOWED;
|
return HTTP_STATUS_NOT_ALLOWED;
|
||||||
|
|
||||||
cfg = dvr_config_find_by_name_default(NULL);
|
if((tcp_id = http_stream_preop(hc)) == NULL)
|
||||||
|
return HTTP_STATUS_NOT_ALLOWED;
|
||||||
/* Build muxer config - this takes the defaults from the default dvr config, which is a hack */
|
|
||||||
mc = muxer_container_txt2type(http_arg_get(&hc->hc_req_args, "mux"));
|
|
||||||
if(mc == MC_UNKNOWN) {
|
|
||||||
mc = cfg->dvr_mc;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((str = http_arg_get(&hc->hc_req_args, "qsize")))
|
if ((str = http_arg_get(&hc->hc_req_args, "qsize")))
|
||||||
qsize = atoll(str);
|
qsize = atoll(str);
|
||||||
else
|
else
|
||||||
qsize = 1500000;
|
qsize = 1500000;
|
||||||
|
|
||||||
if(mc == MC_PASS || mc == MC_RAW) {
|
if (!pro->pro_open(pro, &prch, NULL, qsize)) {
|
||||||
streaming_queue_init2(&sq, SMT_PACKET, qsize);
|
|
||||||
gh = NULL;
|
tcp_get_ip_str((struct sockaddr*)hc->hc_peer, addrbuf, 50);
|
||||||
tsfix = NULL;
|
|
||||||
st = &sq.sq_st;
|
s = subscription_create_from_service(service, weight ?: 100, "HTTP",
|
||||||
flags |= SUBSCRIPTION_RAW_MPEGTS;
|
prch.prch_st,
|
||||||
} else {
|
prch.prch_flags | SUBSCRIPTION_STREAMING,
|
||||||
streaming_queue_init2(&sq, 0, qsize);
|
addrbuf,
|
||||||
gh = globalheaders_create(&sq.sq_st);
|
hc->hc_username,
|
||||||
tsfix = tsfix_create(gh);
|
http_arg_get(&hc->hc_args, "User-Agent"));
|
||||||
st = tsfix;
|
if(s) {
|
||||||
|
name = tvh_strdupa(service->s_nicename);
|
||||||
|
pthread_mutex_unlock(&global_lock);
|
||||||
|
http_stream_run(hc, &prch, name, s);
|
||||||
|
pthread_mutex_lock(&global_lock);
|
||||||
|
subscription_unsubscribe(s);
|
||||||
|
res = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tcp_get_ip_str((struct sockaddr*)hc->hc_peer, addrbuf, 50);
|
profile_chain_close(&prch);
|
||||||
|
|
||||||
s = subscription_create_from_service(service, weight ?: 100, "HTTP", st, flags,
|
|
||||||
addrbuf,
|
|
||||||
hc->hc_username,
|
|
||||||
http_arg_get(&hc->hc_args, "User-Agent"));
|
|
||||||
if(s) {
|
|
||||||
name = tvh_strdupa(service->s_nicename);
|
|
||||||
pthread_mutex_unlock(&global_lock);
|
|
||||||
http_stream_run(hc, &sq, name, mc, s, &cfg->dvr_muxcnf);
|
|
||||||
pthread_mutex_lock(&global_lock);
|
|
||||||
subscription_unsubscribe(s);
|
|
||||||
} else {
|
|
||||||
res = HTTP_STATUS_BAD_REQUEST;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(gh)
|
|
||||||
globalheaders_destroy(gh);
|
|
||||||
|
|
||||||
if(tsfix)
|
|
||||||
tsfix_destroy(tsfix);
|
|
||||||
|
|
||||||
streaming_queue_deinit(&sq);
|
|
||||||
|
|
||||||
http_stream_postop(tcp_id);
|
http_stream_postop(tcp_id);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
@ -870,17 +839,17 @@ http_stream_service(http_connection_t *hc, service_t *service, int weight)
|
||||||
* TODO: can't currently force this to be on a particular input
|
* TODO: can't currently force this to be on a particular input
|
||||||
*/
|
*/
|
||||||
#if ENABLE_MPEGTS
|
#if ENABLE_MPEGTS
|
||||||
#include "input.h"
|
|
||||||
static int
|
static int
|
||||||
http_stream_mux(http_connection_t *hc, mpegts_mux_t *mm, int weight)
|
http_stream_mux(http_connection_t *hc, mpegts_mux_t *mm, int weight)
|
||||||
{
|
{
|
||||||
th_subscription_t *s;
|
th_subscription_t *s;
|
||||||
streaming_queue_t sq;
|
profile_chain_t prch;
|
||||||
|
size_t qsize;
|
||||||
const char *name;
|
const char *name;
|
||||||
char addrbuf[50];
|
char addrbuf[50];
|
||||||
muxer_config_t muxcfg = { 0 };
|
|
||||||
void *tcp_id;
|
void *tcp_id;
|
||||||
int res = 0;
|
const char *str;
|
||||||
|
int res = HTTP_STATUS_BAD_REQUEST;
|
||||||
|
|
||||||
if(http_access_verify(hc, ACCESS_ADVANCED_STREAMING))
|
if(http_access_verify(hc, ACCESS_ADVANCED_STREAMING))
|
||||||
return HTTP_STATUS_UNAUTHORIZED;
|
return HTTP_STATUS_UNAUTHORIZED;
|
||||||
|
@ -888,27 +857,33 @@ http_stream_mux(http_connection_t *hc, mpegts_mux_t *mm, int weight)
|
||||||
if((tcp_id = http_stream_preop(hc)) == NULL)
|
if((tcp_id = http_stream_preop(hc)) == NULL)
|
||||||
return HTTP_STATUS_NOT_ALLOWED;
|
return HTTP_STATUS_NOT_ALLOWED;
|
||||||
|
|
||||||
streaming_queue_init(&sq, SMT_PACKET);
|
if ((str = http_arg_get(&hc->hc_req_args, "qsize")))
|
||||||
|
qsize = atoll(str);
|
||||||
|
else
|
||||||
|
qsize = 10000000;
|
||||||
|
|
||||||
tcp_get_ip_str((struct sockaddr*)hc->hc_peer, addrbuf, 50);
|
if (!profile_chain_raw_open(&prch, qsize)) {
|
||||||
s = subscription_create_from_mux(mm, weight ?: 10, "HTTP", &sq.sq_st,
|
|
||||||
SUBSCRIPTION_RAW_MPEGTS |
|
tcp_get_ip_str((struct sockaddr*)hc->hc_peer, addrbuf, 50);
|
||||||
SUBSCRIPTION_FULLMUX |
|
|
||||||
SUBSCRIPTION_STREAMING,
|
s = subscription_create_from_mux(mm, weight ?: 10, "HTTP",
|
||||||
addrbuf, hc->hc_username,
|
prch.prch_st,
|
||||||
http_arg_get(&hc->hc_args, "User-Agent"), NULL);
|
prch.prch_flags |
|
||||||
if (s) {
|
SUBSCRIPTION_FULLMUX |
|
||||||
name = tvh_strdupa(s->ths_title);
|
SUBSCRIPTION_STREAMING,
|
||||||
pthread_mutex_unlock(&global_lock);
|
addrbuf, hc->hc_username,
|
||||||
http_stream_run(hc, &sq, name, MC_RAW, s, &muxcfg);
|
http_arg_get(&hc->hc_args, "User-Agent"), NULL);
|
||||||
pthread_mutex_lock(&global_lock);
|
if (s) {
|
||||||
subscription_unsubscribe(s);
|
name = tvh_strdupa(s->ths_title);
|
||||||
} else {
|
pthread_mutex_unlock(&global_lock);
|
||||||
res = HTTP_STATUS_BAD_REQUEST;
|
http_stream_run(hc, &prch, name, s);
|
||||||
|
pthread_mutex_lock(&global_lock);
|
||||||
|
subscription_unsubscribe(s);
|
||||||
|
res = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
streaming_queue_deinit(&sq);
|
profile_chain_close(&prch);
|
||||||
|
|
||||||
http_stream_postop(tcp_id);
|
http_stream_postop(tcp_id);
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
@ -921,93 +896,50 @@ http_stream_mux(http_connection_t *hc, mpegts_mux_t *mm, int weight)
|
||||||
static int
|
static int
|
||||||
http_stream_channel(http_connection_t *hc, channel_t *ch, int weight)
|
http_stream_channel(http_connection_t *hc, channel_t *ch, int weight)
|
||||||
{
|
{
|
||||||
streaming_queue_t sq;
|
|
||||||
th_subscription_t *s;
|
th_subscription_t *s;
|
||||||
streaming_target_t *gh;
|
profile_t *pro;
|
||||||
streaming_target_t *tsfix;
|
profile_chain_t prch;
|
||||||
streaming_target_t *st;
|
|
||||||
#if ENABLE_LIBAV
|
|
||||||
streaming_target_t *tr = NULL;
|
|
||||||
#endif
|
|
||||||
dvr_config_t *cfg;
|
|
||||||
int flags = SUBSCRIPTION_STREAMING;
|
|
||||||
muxer_container_type_t mc;
|
|
||||||
char *str;
|
char *str;
|
||||||
size_t qsize;
|
size_t qsize;
|
||||||
const char *name;
|
const char *name;
|
||||||
char addrbuf[50];
|
char addrbuf[50];
|
||||||
void *tcp_id;
|
void *tcp_id;
|
||||||
int res = 0;
|
int res = HTTP_STATUS_BAD_REQUEST;
|
||||||
|
|
||||||
if (http_access_verify_channel(hc, ACCESS_STREAMING, ch, 1))
|
if (http_access_verify_channel(hc, ACCESS_STREAMING, ch, 1))
|
||||||
return HTTP_STATUS_UNAUTHORIZED;
|
return HTTP_STATUS_UNAUTHORIZED;
|
||||||
|
|
||||||
if((tcp_id = http_stream_preop(hc)) == NULL)
|
if(!(pro = profile_find_by_name(http_arg_get(&hc->hc_req_args, "profile"))))
|
||||||
return HTTP_STATUS_NOT_ALLOWED;
|
return HTTP_STATUS_NOT_ALLOWED;
|
||||||
|
|
||||||
cfg = dvr_config_find_by_name_default(NULL);
|
if((tcp_id = http_stream_preop(hc)) == NULL)
|
||||||
|
return HTTP_STATUS_NOT_ALLOWED;
|
||||||
/* Build muxer config - this takes the defaults from the default dvr config, which is a hack */
|
|
||||||
mc = muxer_container_txt2type(http_arg_get(&hc->hc_req_args, "mux"));
|
|
||||||
if(mc == MC_UNKNOWN) {
|
|
||||||
mc = cfg->dvr_mc;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((str = http_arg_get(&hc->hc_req_args, "qsize")))
|
if ((str = http_arg_get(&hc->hc_req_args, "qsize")))
|
||||||
qsize = atoll(str);
|
qsize = atoll(str);
|
||||||
else
|
else
|
||||||
qsize = 1500000;
|
qsize = 1500000;
|
||||||
|
|
||||||
if(mc == MC_PASS || mc == MC_RAW) {
|
if (!pro->pro_open(pro, &prch, NULL, qsize)) {
|
||||||
streaming_queue_init2(&sq, SMT_PACKET, qsize);
|
|
||||||
gh = NULL;
|
tcp_get_ip_str((struct sockaddr*)hc->hc_peer, addrbuf, 50);
|
||||||
tsfix = NULL;
|
|
||||||
st = &sq.sq_st;
|
s = subscription_create_from_channel(ch, weight ?: 100, "HTTP",
|
||||||
flags |= SUBSCRIPTION_RAW_MPEGTS;
|
prch.prch_st, prch.prch_flags | SUBSCRIPTION_STREAMING,
|
||||||
} else {
|
addrbuf, hc->hc_username,
|
||||||
streaming_queue_init2(&sq, 0, qsize);
|
http_arg_get(&hc->hc_args, "User-Agent"));
|
||||||
gh = globalheaders_create(&sq.sq_st);
|
|
||||||
#if ENABLE_LIBAV
|
if(s) {
|
||||||
transcoder_props_t props;
|
name = tvh_strdupa(channel_get_name(ch));
|
||||||
if(http_get_transcoder_properties(&hc->hc_req_args, &props)) {
|
pthread_mutex_unlock(&global_lock);
|
||||||
tr = transcoder_create(gh);
|
http_stream_run(hc, &prch, name, s);
|
||||||
transcoder_set_properties(tr, &props);
|
pthread_mutex_lock(&global_lock);
|
||||||
tsfix = tsfix_create(tr);
|
subscription_unsubscribe(s);
|
||||||
} else
|
res = 0;
|
||||||
#endif
|
}
|
||||||
tsfix = tsfix_create(gh);
|
|
||||||
st = tsfix;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tcp_get_ip_str((struct sockaddr*)hc->hc_peer, addrbuf, 50);
|
profile_chain_close(&prch);
|
||||||
s = subscription_create_from_channel(ch, weight ?: 100, "HTTP", st, flags,
|
|
||||||
addrbuf,
|
|
||||||
hc->hc_username,
|
|
||||||
http_arg_get(&hc->hc_args, "User-Agent"));
|
|
||||||
|
|
||||||
if(s) {
|
|
||||||
name = tvh_strdupa(channel_get_name(ch));
|
|
||||||
pthread_mutex_unlock(&global_lock);
|
|
||||||
http_stream_run(hc, &sq, name, mc, s, &cfg->dvr_muxcnf);
|
|
||||||
pthread_mutex_lock(&global_lock);
|
|
||||||
subscription_unsubscribe(s);
|
|
||||||
} else {
|
|
||||||
res = HTTP_STATUS_BAD_REQUEST;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(gh)
|
|
||||||
globalheaders_destroy(gh);
|
|
||||||
|
|
||||||
#if ENABLE_LIBAV
|
|
||||||
if(tr)
|
|
||||||
transcoder_destroy(tr);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if(tsfix)
|
|
||||||
tsfix_destroy(tsfix);
|
|
||||||
|
|
||||||
streaming_queue_deinit(&sq);
|
|
||||||
|
|
||||||
http_stream_postop(tcp_id);
|
http_stream_postop(tcp_id);
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
|
Loading…
Add table
Reference in a new issue