
- also added alternate default lookups for profiles (not defined) for * DVR : 'dvr' * Channel streaming : 'channel' * Service streaming : 'service'
1071 lines
26 KiB
C
1071 lines
26 KiB
C
/*
|
|
* 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 "access.h"
|
|
#include "plumbing/tsfix.h"
|
|
#include "plumbing/globalheaders.h"
|
|
#if ENABLE_LIBAV
|
|
#include "lang_codes.h"
|
|
#include "plumbing/transcoding.h"
|
|
#endif
|
|
#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);
|
|
return NULL;
|
|
}
|
|
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();
|
|
if (pro == profile_default)
|
|
pro->pro_enabled = 1;
|
|
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 uint32_t
|
|
profile_class_enabled_opts(void *o)
|
|
{
|
|
profile_t *pro = o;
|
|
uint32_t r = 0;
|
|
if (pro && profile_default == pro)
|
|
r |= PO_RDONLY;
|
|
return r;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static uint32_t
|
|
profile_class_name_opts(void *o)
|
|
{
|
|
profile_t *pro = o;
|
|
uint32_t r = 0;
|
|
if (pro && pro->pro_shield)
|
|
r |= PO_RDONLY;
|
|
return r;
|
|
}
|
|
|
|
const idclass_t profile_class =
|
|
{
|
|
.ic_class = "profile",
|
|
.ic_caption = "Stream Profile",
|
|
.ic_event = "profile",
|
|
.ic_perm_def = ACCESS_ADMIN,
|
|
.ic_save = profile_class_save,
|
|
.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),
|
|
.get_opts = profile_class_enabled_opts,
|
|
},
|
|
{
|
|
.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),
|
|
.get_opts = profile_class_name_opts,
|
|
.notify = idnode_notify_title_changed,
|
|
},
|
|
{
|
|
.type = PT_STR,
|
|
.id = "comment",
|
|
.name = "Comment",
|
|
.off = offsetof(profile_t, pro_comment),
|
|
},
|
|
{
|
|
.type = PT_INT,
|
|
.id = "timeout",
|
|
.name = "Timeout (sec) (0=infinite)",
|
|
.off = offsetof(profile_t, pro_timeout),
|
|
.def.i = 5,
|
|
},
|
|
{
|
|
.type = PT_BOOL,
|
|
.id = "restart",
|
|
.name = "Restart On Error",
|
|
.off = offsetof(profile_t, pro_restart),
|
|
.def.i = 0,
|
|
},
|
|
{ }
|
|
}
|
|
};
|
|
|
|
/*
|
|
*
|
|
*/
|
|
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, const char *alt)
|
|
{
|
|
profile_t *pro;
|
|
|
|
lock_assert(&global_lock);
|
|
|
|
if (!name && alt) {
|
|
name = alt;
|
|
alt = NULL;
|
|
}
|
|
|
|
if (!name)
|
|
return profile_default;
|
|
|
|
TAILQ_FOREACH(pro, &profiles, pro_link) {
|
|
if (!strcmp(pro->pro_name, name))
|
|
return pro;
|
|
}
|
|
|
|
if (alt) {
|
|
TAILQ_FOREACH(pro, &profiles, pro_link) {
|
|
if (!strcmp(pro->pro_name, alt))
|
|
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;
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
void
|
|
profile_get_htsp_list(htsmsg_t *array)
|
|
{
|
|
profile_t *pro;
|
|
htsmsg_t *m;
|
|
|
|
TAILQ_FOREACH(pro, &profiles, pro_link) {
|
|
if (pro->pro_work) {
|
|
m = htsmsg_create_map();
|
|
htsmsg_add_str(m, "uuid", idnode_uuid_as_str(&pro->pro_id));
|
|
htsmsg_add_str(m, "name", pro->pro_name ?: "");
|
|
htsmsg_add_str(m, "comment", pro->pro_comment ?: "");
|
|
htsmsg_add_msg(array, NULL, 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 ENABLE_LIBAV
|
|
if (prch->prch_transcoder)
|
|
transcoder_destroy(prch->prch_transcoder);
|
|
#endif
|
|
if (prch->prch_muxer)
|
|
muxer_destroy(prch->prch_muxer);
|
|
streaming_queue_deinit(&prch->prch_sq);
|
|
}
|
|
|
|
/*
|
|
* HTSP Profile Class
|
|
*/
|
|
const idclass_t profile_htsp_class =
|
|
{
|
|
.ic_super = &profile_class,
|
|
.ic_class = "profile-htsp",
|
|
.ic_caption = "HTSP Stream Profile",
|
|
.ic_properties = (const property_t[]){
|
|
/* Ready for future extensions */
|
|
{ }
|
|
}
|
|
};
|
|
|
|
static muxer_container_type_t
|
|
profile_htsp_get_mc(profile_t *_pro)
|
|
{
|
|
return MC_UNKNOWN;
|
|
}
|
|
|
|
static profile_t *
|
|
profile_htsp_builder(void)
|
|
{
|
|
profile_t *pro = calloc(1, sizeof(*pro));
|
|
pro->pro_open = NULL;
|
|
pro->pro_get_mc = profile_htsp_get_mc;
|
|
return pro;
|
|
}
|
|
|
|
/*
|
|
* 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, int flags, 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, int flags, 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;
|
|
}
|
|
|
|
#if ENABLE_LIBAV
|
|
|
|
static int profile_transcode_experimental_codecs = 1;
|
|
|
|
/*
|
|
* Transcoding + packet-like muxers
|
|
*/
|
|
typedef struct profile_transcode {
|
|
profile_t;
|
|
int pro_mc;
|
|
uint32_t pro_resolution;
|
|
uint32_t pro_channels;
|
|
uint32_t pro_bandwidth;
|
|
char *pro_language;
|
|
char *pro_vcodec;
|
|
char *pro_acodec;
|
|
char *pro_scodec;
|
|
} profile_transcode_t;
|
|
|
|
static htsmsg_t *
|
|
profile_class_mc_list ( void *o )
|
|
{
|
|
static const struct strtab tab[] = {
|
|
{ "Not set", MC_UNKNOWN },
|
|
{ "Matroska (mkv)", MC_MATROSKA, },
|
|
{ "WEBM", MC_WEBM, },
|
|
{ "MPEG-TS", MC_MPEGTS },
|
|
{ "MPEG-PS (DVD)", MC_MPEGPS },
|
|
};
|
|
return strtab2htsmsg(tab);
|
|
}
|
|
|
|
static htsmsg_t *
|
|
profile_class_channels_list ( void *o )
|
|
{
|
|
static const struct strtab tab[] = {
|
|
{ "Copy layout", 0 },
|
|
{ "Mono", 1 },
|
|
{ "Stereo", 2 },
|
|
{ "Surround (2 Front, Rear Mono)", 3 },
|
|
{ "Quad (4.0)", 4 },
|
|
{ "5.0", 5 },
|
|
{ "5.1", 6 },
|
|
{ "6.1", 7 },
|
|
{ "7.1", 8 }
|
|
};
|
|
return strtab2htsmsg(tab);
|
|
}
|
|
|
|
static htsmsg_t *
|
|
profile_class_language_list(void *o)
|
|
{
|
|
htsmsg_t *l = htsmsg_create_list();
|
|
const lang_code_t *lc = lang_codes;
|
|
char buf[128];
|
|
|
|
while (lc->code2b) {
|
|
htsmsg_t *e = htsmsg_create_map();
|
|
if (!strcmp(lc->code2b, "und")) {
|
|
htsmsg_add_str(e, "key", "");
|
|
htsmsg_add_str(e, "val", "Use original");
|
|
} else {
|
|
htsmsg_add_str(e, "key", lc->code2b);
|
|
snprintf(buf, sizeof(buf), "%s (%s)", lc->desc, lc->code2b);
|
|
buf[sizeof(buf)-1] = '\0';
|
|
htsmsg_add_str(e, "val", buf);
|
|
}
|
|
htsmsg_add_msg(l, NULL, e);
|
|
lc++;
|
|
}
|
|
return l;
|
|
}
|
|
|
|
static inline int
|
|
profile_class_check_sct(htsmsg_t *c, int sct)
|
|
{
|
|
htsmsg_field_t *f;
|
|
int64_t x;
|
|
HTSMSG_FOREACH(f, c)
|
|
if (!htsmsg_field_get_s64(f, &x))
|
|
if (x == sct)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static htsmsg_t *
|
|
profile_class_codec_list(int (*check)(int sct))
|
|
{
|
|
htsmsg_t *l = htsmsg_create_list(), *e, *c, *m;
|
|
htsmsg_field_t *f;
|
|
const char *s, *s2;
|
|
char buf[128];
|
|
int sct;
|
|
|
|
e = htsmsg_create_map();
|
|
htsmsg_add_str(e, "key", "");
|
|
htsmsg_add_str(e, "val", "Do not use");
|
|
htsmsg_add_msg(l, NULL, e);
|
|
e = htsmsg_create_map();
|
|
htsmsg_add_str(e, "key", "copy");
|
|
htsmsg_add_str(e, "val", "Copy codec type");
|
|
htsmsg_add_msg(l, NULL, e);
|
|
c = transcoder_get_capabilities(profile_transcode_experimental_codecs);
|
|
HTSMSG_FOREACH(f, c) {
|
|
if (!(m = htsmsg_field_get_map(f)))
|
|
continue;
|
|
if (htsmsg_get_s32(m, "type", &sct))
|
|
continue;
|
|
if (!check(sct))
|
|
continue;
|
|
if (!(s = htsmsg_get_str(m, "name")))
|
|
continue;
|
|
s2 = htsmsg_get_str(m, "long_name");
|
|
if (s2)
|
|
snprintf(buf, sizeof(buf), "%s: %s", s, s2);
|
|
else
|
|
snprintf(buf, sizeof(buf), "%s", s);
|
|
e = htsmsg_create_map();
|
|
htsmsg_add_str(e, "key", s);
|
|
htsmsg_add_str(e, "val", buf);
|
|
htsmsg_add_msg(l, NULL, e);
|
|
}
|
|
htsmsg_destroy(c);
|
|
return l;
|
|
}
|
|
|
|
static int
|
|
profile_class_vcodec_sct_check(int sct)
|
|
{
|
|
return SCT_ISVIDEO(sct);
|
|
}
|
|
|
|
static htsmsg_t *
|
|
profile_class_vcodec_list(void *o)
|
|
{
|
|
return profile_class_codec_list(profile_class_vcodec_sct_check);
|
|
}
|
|
|
|
static int
|
|
profile_class_acodec_sct_check(int sct)
|
|
{
|
|
return SCT_ISAUDIO(sct);
|
|
}
|
|
|
|
static htsmsg_t *
|
|
profile_class_acodec_list(void *o)
|
|
{
|
|
return profile_class_codec_list(profile_class_acodec_sct_check);
|
|
}
|
|
|
|
static int
|
|
profile_class_scodec_sct_check(int sct)
|
|
{
|
|
return SCT_ISSUBTITLE(sct);
|
|
}
|
|
|
|
static htsmsg_t *
|
|
profile_class_scodec_list(void *o)
|
|
{
|
|
return profile_class_codec_list(profile_class_scodec_sct_check);
|
|
}
|
|
|
|
const idclass_t profile_transcode_class =
|
|
{
|
|
.ic_super = &profile_class,
|
|
.ic_class = "profile-transcode",
|
|
.ic_caption = "Transcode",
|
|
.ic_properties = (const property_t[]){
|
|
{
|
|
.type = PT_INT,
|
|
.id = "container",
|
|
.name = "Container",
|
|
.off = offsetof(profile_transcode_t, pro_mc),
|
|
.def.i = MC_MATROSKA,
|
|
.list = profile_class_mc_list,
|
|
},
|
|
{
|
|
.type = PT_U32,
|
|
.id = "resolution",
|
|
.name = "Resolution",
|
|
.off = offsetof(profile_transcode_t, pro_resolution),
|
|
.def.u32 = 384,
|
|
},
|
|
{
|
|
.type = PT_U32,
|
|
.id = "channels",
|
|
.name = "Channels",
|
|
.off = offsetof(profile_transcode_t, pro_channels),
|
|
.def.u32 = 2,
|
|
.list = profile_class_channels_list,
|
|
},
|
|
{
|
|
.type = PT_STR,
|
|
.id = "language",
|
|
.name = "Language",
|
|
.off = offsetof(profile_transcode_t, pro_language),
|
|
.list = profile_class_language_list,
|
|
},
|
|
{
|
|
.type = PT_STR,
|
|
.id = "vcodec",
|
|
.name = "Video Codec",
|
|
.off = offsetof(profile_transcode_t, pro_vcodec),
|
|
.def.s = "libx264",
|
|
.list = profile_class_vcodec_list,
|
|
},
|
|
{
|
|
.type = PT_STR,
|
|
.id = "acodec",
|
|
.name = "Audio Codec",
|
|
.off = offsetof(profile_transcode_t, pro_acodec),
|
|
.def.s = "libvorbis",
|
|
.list = profile_class_acodec_list,
|
|
},
|
|
{
|
|
.type = PT_STR,
|
|
.id = "scodec",
|
|
.name = "Subtitles Codec",
|
|
.off = offsetof(profile_transcode_t, pro_scodec),
|
|
.def.s = "",
|
|
.list = profile_class_scodec_list,
|
|
},
|
|
{ }
|
|
}
|
|
};
|
|
|
|
static void
|
|
profile_transcode_work_destroy(streaming_target_t *st)
|
|
{
|
|
if (st)
|
|
transcoder_destroy(st);
|
|
}
|
|
|
|
static streaming_target_t *
|
|
profile_transcode_work(profile_t *_pro, streaming_target_t *src,
|
|
void (**destroy)(streaming_target_t *st))
|
|
{
|
|
profile_transcode_t *pro = (profile_transcode_t *)_pro;
|
|
transcoder_props_t props;
|
|
streaming_target_t *st;
|
|
|
|
memset(&props, 0, sizeof(props));
|
|
strncpy(props.tp_vcodec, pro->pro_vcodec ?: "", sizeof(props.tp_vcodec)-1);
|
|
strncpy(props.tp_acodec, pro->pro_acodec ?: "", sizeof(props.tp_acodec)-1);
|
|
strncpy(props.tp_scodec, pro->pro_scodec ?: "", sizeof(props.tp_scodec)-1);
|
|
props.tp_resolution = pro->pro_resolution >= 240 ? pro->pro_resolution : 240;
|
|
props.tp_channels = pro->pro_channels;
|
|
props.tp_bandwidth = pro->pro_bandwidth >= 64 ? pro->pro_bandwidth : 64;
|
|
strncpy(props.tp_language, pro->pro_language ?: "", 3);
|
|
|
|
if (destroy)
|
|
*destroy = profile_transcode_work_destroy;
|
|
|
|
st = transcoder_create(src);
|
|
if (st)
|
|
transcoder_set_properties(st, &props);
|
|
return st;
|
|
}
|
|
|
|
static int
|
|
profile_transcode_mc_valid(int mc)
|
|
{
|
|
switch (mc) {
|
|
case MC_MATROSKA:
|
|
case MC_WEBM:
|
|
case MC_MPEGTS:
|
|
case MC_MPEGPS:
|
|
return 1;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static int
|
|
profile_transcode_open(profile_t *_pro, profile_chain_t *prch,
|
|
muxer_config_t *m_cfg, int flags, size_t qsize)
|
|
{
|
|
profile_transcode_t *pro = (profile_transcode_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 (!profile_transcode_mc_valid(c.m_type)) {
|
|
c.m_type = pro->pro_mc;
|
|
if (!profile_transcode_mc_valid(c.m_type))
|
|
c.m_type = MC_MATROSKA;
|
|
}
|
|
|
|
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_transcoder = profile_transcode_work(_pro, prch->prch_gh, NULL);
|
|
prch->prch_tsfix = tsfix_create(prch->prch_transcoder);
|
|
prch->prch_muxer = muxer_create(&c);
|
|
prch->prch_st = prch->prch_tsfix;
|
|
return 0;
|
|
}
|
|
|
|
static muxer_container_type_t
|
|
profile_transcode_get_mc(profile_t *_pro)
|
|
{
|
|
profile_transcode_t *pro = (profile_transcode_t *)_pro;
|
|
return pro->pro_mc;
|
|
}
|
|
|
|
static void
|
|
profile_transcode_free(profile_t *_pro)
|
|
{
|
|
profile_transcode_t *pro = (profile_transcode_t *)_pro;
|
|
free(pro->pro_vcodec);
|
|
free(pro->pro_acodec);
|
|
free(pro->pro_scodec);
|
|
}
|
|
|
|
static profile_t *
|
|
profile_transcode_builder(void)
|
|
{
|
|
profile_transcode_t *pro = calloc(1, sizeof(*pro));
|
|
pro->pro_free = profile_transcode_free;
|
|
pro->pro_work = profile_transcode_work;
|
|
pro->pro_open = profile_transcode_open;
|
|
pro->pro_get_mc = profile_transcode_get_mc;
|
|
return (profile_t *)pro;
|
|
}
|
|
|
|
#endif /* ENABLE_TRANSCODE */
|
|
|
|
/*
|
|
* Initialize
|
|
*/
|
|
void
|
|
profile_init(void)
|
|
{
|
|
htsmsg_t *c, *e;
|
|
htsmsg_field_t *f;
|
|
profile_t *pro;
|
|
const char *name;
|
|
|
|
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);
|
|
profile_register(&profile_htsp_class, profile_htsp_builder);
|
|
#if ENABLE_LIBAV
|
|
profile_transcode_experimental_codecs =
|
|
getenv("TVHEADEND_LIBAV_NO_EXPERIMENTAL_CODECS") ? 0 : 1;
|
|
profile_register(&profile_transcode_class, profile_transcode_builder);
|
|
#endif
|
|
|
|
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);
|
|
}
|
|
|
|
name = "pass";
|
|
pro = profile_find_by_name(name, NULL);
|
|
if (pro == NULL || strcmp(pro->pro_name, name)) {
|
|
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", name);
|
|
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);
|
|
}
|
|
|
|
name = "matroska";
|
|
pro = profile_find_by_name(name, NULL);
|
|
if (pro == NULL || strcmp(pro->pro_name, name)) {
|
|
htsmsg_t *conf;
|
|
|
|
conf = htsmsg_create_map();
|
|
htsmsg_add_str (conf, "class", "profile-matroska");
|
|
htsmsg_add_bool(conf, "enabled", 1);
|
|
htsmsg_add_str (conf, "name", name);
|
|
htsmsg_add_str (conf, "comment", "Matroska");
|
|
htsmsg_add_bool(conf, "shield", 1);
|
|
(void)profile_create(NULL, conf, 1);
|
|
htsmsg_destroy(conf);
|
|
}
|
|
|
|
name = "htsp";
|
|
pro = profile_find_by_name(name, NULL);
|
|
if (pro == NULL || strcmp(pro->pro_name, name)) {
|
|
htsmsg_t *conf;
|
|
|
|
conf = htsmsg_create_map();
|
|
htsmsg_add_str (conf, "class", "profile-htsp");
|
|
htsmsg_add_bool(conf, "enabled", 1);
|
|
htsmsg_add_str (conf, "name", name);
|
|
htsmsg_add_str (conf, "comment", "HTSP Default Stream Settings");
|
|
htsmsg_add_bool(conf, "shield", 1);
|
|
(void)profile_create(NULL, conf, 1);
|
|
htsmsg_destroy(conf);
|
|
}
|
|
|
|
#if ENABLE_LIBAV
|
|
|
|
name = "webtv-vp8-vorbis-webm";
|
|
pro = profile_find_by_name(name, NULL);
|
|
if (pro == NULL || strcmp(pro->pro_name, name)) {
|
|
htsmsg_t *conf;
|
|
|
|
conf = htsmsg_create_map();
|
|
htsmsg_add_str (conf, "class", "profile-transcode");
|
|
htsmsg_add_bool(conf, "enabled", 1);
|
|
htsmsg_add_str (conf, "name", name);
|
|
htsmsg_add_str (conf, "comment", "WEBTV profile VP8/Vorbis/WEBM");
|
|
htsmsg_add_s32 (conf, "container", MC_WEBM);
|
|
htsmsg_add_u32 (conf, "resolution", 384);
|
|
htsmsg_add_u32 (conf, "channels", 2);
|
|
htsmsg_add_str (conf, "vcodec", "libvpx");
|
|
htsmsg_add_str (conf, "acodec", "libvorbis");
|
|
htsmsg_add_bool(conf, "shield", 1);
|
|
(void)profile_create(NULL, conf, 1);
|
|
htsmsg_destroy(conf);
|
|
}
|
|
name = "webtv-h264-aac-mpegts";
|
|
pro = profile_find_by_name(name, NULL);
|
|
if (pro == NULL || strcmp(pro->pro_name, name)) {
|
|
htsmsg_t *conf;
|
|
|
|
conf = htsmsg_create_map();
|
|
htsmsg_add_str (conf, "class", "profile-transcode");
|
|
htsmsg_add_bool(conf, "enabled", 1);
|
|
htsmsg_add_str (conf, "name", name);
|
|
htsmsg_add_str (conf, "comment", "WEBTV profile H264/AAC/MPEG-TS");
|
|
htsmsg_add_s32 (conf, "container", MC_MPEGTS);
|
|
htsmsg_add_u32 (conf, "resolution", 384);
|
|
htsmsg_add_u32 (conf, "channels", 2);
|
|
htsmsg_add_str (conf, "vcodec", "libx264");
|
|
htsmsg_add_str (conf, "acodec", "aac");
|
|
htsmsg_add_bool(conf, "shield", 1);
|
|
(void)profile_create(NULL, conf, 1);
|
|
htsmsg_destroy(conf);
|
|
}
|
|
name = "webtv-h264-aac-matroska";
|
|
pro = profile_find_by_name(name, NULL);
|
|
if (pro == NULL || strcmp(pro->pro_name, name)) {
|
|
htsmsg_t *conf;
|
|
|
|
conf = htsmsg_create_map();
|
|
htsmsg_add_str (conf, "class", "profile-transcode");
|
|
htsmsg_add_bool(conf, "enabled", 1);
|
|
htsmsg_add_str (conf, "name", name);
|
|
htsmsg_add_str (conf, "comment", "WEBTV profile H264/AAC/Matroska");
|
|
htsmsg_add_s32 (conf, "container", MC_MATROSKA);
|
|
htsmsg_add_u32 (conf, "resolution", 384);
|
|
htsmsg_add_u32 (conf, "channels", 2);
|
|
htsmsg_add_str (conf, "vcodec", "libx264");
|
|
htsmsg_add_str (conf, "acodec", "aac");
|
|
htsmsg_add_bool(conf, "shield", 1);
|
|
(void)profile_create(NULL, conf, 1);
|
|
htsmsg_destroy(conf);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
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);
|
|
}
|