1 /*
2 * tvheadend, Stream Profile
3 * Copyright (C) 2014 Jaroslav Kysela
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "tvheadend.h"
20 #include "settings.h"
21 #include "profile.h"
22 #include "streaming.h"
23 #include "access.h"
24 #include "plumbing/tsfix.h"
25 #include "plumbing/globalheaders.h"
26 #if ENABLE_LIBAV
27 #include "lang_codes.h"
28 #include "plumbing/transcoding.h"
29 #endif
30 #if ENABLE_TIMESHIFT
31 #include "timeshift.h"
32 #endif
33 #include "dvr/dvr.h"
34
35 extern const idclass_t profile_htsp_class;
36
37 profile_builders_queue profile_builders;
38
39 struct profile_entry_queue profiles;
40 static LIST_HEAD(,profile_chain) profile_chains;
41
42 static profile_t *profile_default;
43
44 /*
45 *
46 */
47
48 void
profile_register(const idclass_t * clazz,profile_builder_t builder)49 profile_register(const idclass_t *clazz, profile_builder_t builder)
50 {
51 profile_build_t *pb = calloc(1, sizeof(*pb)), *pb2;
52 idclass_register(clazz);
53 pb->clazz = clazz;
54 pb->build = builder;
55 pb2 = LIST_FIRST(&profile_builders);
56 if (pb2) {
57 /* append tail */
58 while (LIST_NEXT(pb2, link))
59 pb2 = LIST_NEXT(pb2, link);
60 LIST_INSERT_AFTER(pb2, pb, link);
61 } else {
62 LIST_INSERT_HEAD(&profile_builders, pb, link);
63 }
64 }
65
66 static profile_build_t *
profile_class_find(const char * name)67 profile_class_find(const char *name)
68 {
69 profile_build_t *pb;
70 LIST_FOREACH(pb, &profile_builders, link) {
71 if (strcmp(pb->clazz->ic_class, name) == 0)
72 return pb;
73 }
74 return NULL;
75 }
76
77 profile_t *
profile_create(const char * uuid,htsmsg_t * conf,int save)78 profile_create
79 (const char *uuid, htsmsg_t *conf, int save)
80 {
81 profile_t *pro = NULL;
82 profile_build_t *pb = NULL;
83 const char *s;
84
85 lock_assert(&global_lock);
86
87 if ((s = htsmsg_get_str(conf, "class")) != NULL)
88 pb = profile_class_find(s);
89 if (pb == NULL) {
90 tvherror(LS_PROFILE, "wrong class %s!", s);
91 return NULL;
92 }
93 pro = pb->build();
94 if (pro == NULL) {
95 tvherror(LS_PROFILE, "Profile class %s is not available!", s);
96 return NULL;
97 }
98 LIST_INIT(&pro->pro_dvr_configs);
99 LIST_INIT(&pro->pro_accesses);
100 pro->pro_swservice = 1;
101 pro->pro_contaccess = 1;
102 pro->pro_ca_timeout = 2000;
103 if (idnode_insert(&pro->pro_id, uuid, pb->clazz, 0)) {
104 if (uuid)
105 tvherror(LS_PROFILE, "invalid uuid '%s'", uuid);
106 free(pro);
107 return NULL;
108 }
109 if (conf) {
110 int b;
111 idnode_load(&pro->pro_id, conf);
112 if (!htsmsg_get_bool(conf, "shield", &b))
113 pro->pro_shield = !!b;
114 }
115 pro->pro_refcount = 1;
116 TAILQ_INSERT_TAIL(&profiles, pro, pro_link);
117 if (save)
118 idnode_changed(&pro->pro_id);
119 if (pro->pro_conf_changed)
120 pro->pro_conf_changed(pro);
121 return pro;
122 }
123
124 void
profile_release_(profile_t * pro)125 profile_release_(profile_t *pro)
126 {
127 if (pro->pro_free)
128 pro->pro_free(pro);
129 free(pro->pro_name);
130 free(pro->pro_comment);
131 free(pro);
132 }
133
134 static void
profile_delete(profile_t * pro,int delconf)135 profile_delete(profile_t *pro, int delconf)
136 {
137 char ubuf[UUID_HEX_SIZE];
138 idnode_save_check(&pro->pro_id, delconf);
139 pro->pro_enabled = 0;
140 if (pro->pro_conf_changed)
141 pro->pro_conf_changed(pro);
142 if (delconf)
143 hts_settings_remove("profile/%s", idnode_uuid_as_str(&pro->pro_id, ubuf));
144 TAILQ_REMOVE(&profiles, pro, pro_link);
145 idnode_unlink(&pro->pro_id);
146 dvr_config_destroy_by_profile(pro, delconf);
147 access_destroy_by_profile(pro, delconf);
148 profile_release(pro);
149 }
150
151 static htsmsg_t *
profile_class_save(idnode_t * in,char * filename,size_t fsize)152 profile_class_save ( idnode_t *in, char *filename, size_t fsize )
153 {
154 profile_t *pro = (profile_t *)in;
155 htsmsg_t *c = htsmsg_create_map();
156 char ubuf[UUID_HEX_SIZE];
157 if (pro == profile_default)
158 pro->pro_enabled = 1;
159 idnode_save(in, c);
160 if (pro->pro_shield)
161 htsmsg_add_bool(c, "shield", 1);
162 snprintf(filename, fsize, "profile/%s", idnode_uuid_as_str(in, ubuf));
163 if (pro->pro_conf_changed)
164 pro->pro_conf_changed(pro);
165 return c;
166 }
167
168 static const char *
profile_class_get_title(idnode_t * in,const char * lang)169 profile_class_get_title ( idnode_t *in, const char *lang )
170 {
171 profile_t *pro = (profile_t *)in;
172 if (pro->pro_name && pro->pro_name[0])
173 return pro->pro_name;
174 snprintf(prop_sbuf, sizeof(prop_sbuf), "%s", idclass_get_caption(in->in_class, lang));
175 return prop_sbuf;
176 }
177
178 static void
profile_class_delete(idnode_t * self)179 profile_class_delete(idnode_t *self)
180 {
181 profile_t *pro = (profile_t *)self;
182 if (pro->pro_shield)
183 return;
184 profile_delete(pro, 1);
185 }
186
187 static uint32_t
profile_class_enabled_opts(void * o)188 profile_class_enabled_opts(void *o)
189 {
190 profile_t *pro = o;
191 uint32_t r = 0;
192 if (pro && profile_default == pro)
193 r |= PO_RDONLY;
194 return r;
195 }
196
197 static const void *
profile_class_class_get(void * o)198 profile_class_class_get(void *o)
199 {
200 profile_t *pro = o;
201 static const char *ret;
202 ret = pro->pro_id.in_class->ic_class;
203 return &ret;
204 }
205
206 static int
profile_class_class_set(void * o,const void * v)207 profile_class_class_set(void *o, const void *v)
208 {
209 /* just ignore, create fcn does the right job */
210 return 0;
211 }
212
213 static const void *
profile_class_default_get(void * o)214 profile_class_default_get(void *o)
215 {
216 static int res;
217 res = o == profile_default;
218 return &res;
219 }
220
221 static int
profile_class_default_set(void * o,const void * v)222 profile_class_default_set(void *o, const void *v)
223 {
224 profile_t *pro = o, *old;
225 if (*(int *)v && pro != profile_default) {
226 old = profile_default;
227 profile_default = pro;
228 if (old)
229 idnode_changed(&old->pro_id);
230 return 1;
231 }
232 return 0;
233 }
234
235 static uint32_t
profile_class_name_opts(void * o)236 profile_class_name_opts(void *o)
237 {
238 profile_t *pro = o;
239 uint32_t r = 0;
240 if (pro && pro->pro_shield)
241 r |= PO_RDONLY;
242 return r;
243 }
244
245 static htsmsg_t *
profile_class_priority_list(void * o,const char * lang)246 profile_class_priority_list ( void *o, const char *lang )
247 {
248 static const struct strtab tab[] = {
249 { N_("Unset (default)"), PROFILE_SPRIO_NOTSET },
250 { N_("Important"), PROFILE_SPRIO_IMPORTANT },
251 { N_("High"), PROFILE_SPRIO_HIGH, },
252 { N_("Normal"), PROFILE_SPRIO_NORMAL },
253 { N_("Low"), PROFILE_SPRIO_LOW },
254 { N_("Unimportant"), PROFILE_SPRIO_UNIMPORTANT },
255 { N_("DVR override: important"), PROFILE_SPRIO_DVR_IMPORTANT },
256 { N_("DVR override: high"), PROFILE_SPRIO_DVR_HIGH },
257 { N_("DVR override: normal"), PROFILE_SPRIO_DVR_NORMAL },
258 { N_("DVR override: low"), PROFILE_SPRIO_DVR_LOW },
259 { N_("DVR override: unimportant"), PROFILE_SPRIO_DVR_UNIMPORTANT },
260 };
261 return strtab2htsmsg(tab, 1, lang);
262 }
263
264 static htsmsg_t *
profile_class_svfilter_list(void * o,const char * lang)265 profile_class_svfilter_list ( void *o, const char *lang )
266 {
267 static const struct strtab tab[] = {
268 { N_("None"), PROFILE_SVF_NONE },
269 { N_("SD: standard definition"), PROFILE_SVF_SD },
270 { N_("HD: high definition"), PROFILE_SVF_HD },
271 { N_("UHD: ultra high definition"), PROFILE_SVF_UHD },
272 };
273 return strtab2htsmsg(tab, 1, lang);
274 }
275
276 CLASS_DOC(profile)
277
278 const idclass_t profile_class =
279 {
280 .ic_class = "profile",
281 .ic_caption = N_("Stream Profile"),
282 .ic_event = "profile",
283 .ic_doc = tvh_doc_profile_class,
284 .ic_perm_def = ACCESS_ADMIN,
285 .ic_save = profile_class_save,
286 .ic_get_title = profile_class_get_title,
287 .ic_delete = profile_class_delete,
288 .ic_groups = (const property_group_t[]) {
289 {
290 .name = N_("Configuration"),
291 .number = 1,
292 },
293 {}
294 },
295 .ic_properties = (const property_t[]){
296 {
297 .type = PT_STR,
298 .id = "class",
299 .name = N_("Class"),
300 .opts = PO_RDONLY | PO_HIDDEN,
301 .get = profile_class_class_get,
302 .set = profile_class_class_set,
303 .group = 1
304 },
305 {
306 .type = PT_BOOL,
307 .id = "enabled",
308 .name = N_("Enabled"),
309 .desc = N_("Enable/disable profile."),
310 .off = offsetof(profile_t, pro_enabled),
311 .get_opts = profile_class_enabled_opts,
312 .group = 1,
313 .def.i = 1
314 },
315 {
316 .type = PT_BOOL,
317 .id = "default",
318 .name = N_("Default"),
319 .desc = N_("Set as default profile."),
320 .set = profile_class_default_set,
321 .get = profile_class_default_get,
322 .opts = PO_EXPERT,
323 .group = 1
324 },
325 {
326 .type = PT_STR,
327 .id = "name",
328 .name = N_("Profile name"),
329 .desc = N_("The name of the profile."),
330 .off = offsetof(profile_t, pro_name),
331 .get_opts = profile_class_name_opts,
332 .notify = idnode_notify_title_changed,
333 .group = 1
334 },
335 {
336 .type = PT_STR,
337 .id = "comment",
338 .name = N_("Comment"),
339 .desc = N_("Free-form text field. You can enter whatever you "
340 "like here."),
341 .off = offsetof(profile_t, pro_comment),
342 .group = 1
343 },
344 {
345 .type = PT_INT,
346 .id = "priority",
347 .name = N_("Default priority"),
348 .desc = N_("If no specific priority was requested. This "
349 "gives certain users a higher priority by "
350 "assigning a streaming profile with a higher "
351 "priority."),
352 .list = profile_class_priority_list,
353 .off = offsetof(profile_t, pro_prio),
354 .opts = PO_SORTKEY | PO_ADVANCED,
355 .def.i = PROFILE_SPRIO_NORMAL,
356 .group = 1
357 },
358 {
359 .type = PT_INT,
360 .id = "fpriority",
361 .name = N_("Force priority"),
362 .desc = N_("Force profile to use this priority."),
363 .off = offsetof(profile_t, pro_fprio),
364 .opts = PO_EXPERT,
365 .group = 1
366 },
367 {
368 .type = PT_INT,
369 .id = "timeout",
370 .name = N_("Timeout (sec) (0=infinite)"),
371 .desc = N_("The number of seconds to wait for a stream to "
372 "start."),
373 .off = offsetof(profile_t, pro_timeout),
374 .def.i = 5,
375 .group = 1
376 },
377 {
378 .type = PT_BOOL,
379 .id = "restart",
380 .name = N_("Restart on error"),
381 .desc = N_("Restart streaming on error. This is useful for "
382 "DVR so a recording isn't aborted if an error occurs."),
383 .off = offsetof(profile_t, pro_restart),
384 .opts = PO_EXPERT,
385 .def.i = 0,
386 .group = 1
387 },
388 {
389 .type = PT_BOOL,
390 .id = "contaccess",
391 .name = N_("Continue if descrambling fails"),
392 .desc = N_("Don't abort streaming when an encrypted stream "
393 "can't be decrypted by a CA client that normally "
394 "should be able to decrypt the stream."),
395 .off = offsetof(profile_t, pro_contaccess),
396 .opts = PO_EXPERT,
397 .def.i = 1,
398 .group = 1
399 },
400 {
401 .type = PT_INT,
402 .id = "catimeout",
403 .name = N_("Descrambling timeout (ms)"),
404 .desc = N_("Check the descrambling status after this timeout."),
405 .off = offsetof(profile_t, pro_ca_timeout),
406 .opts = PO_EXPERT,
407 .def.i = 2000,
408 .group = 1
409 },
410 {
411 .type = PT_BOOL,
412 .id = "swservice",
413 .name = N_("Switch to another service"),
414 .desc = N_("If something fails, try to switch to a different "
415 "service on another network. Do not try to iterate "
416 "through all inputs/tuners which are capable to "
417 "receive the service."),
418 .off = offsetof(profile_t, pro_swservice),
419 .opts = PO_EXPERT,
420 .def.i = 1,
421 .group = 1
422 },
423 {
424 .type = PT_INT,
425 .id = "svfilter",
426 .name = N_("Preferred service video type"),
427 .desc = N_("The selected video type should be preferred when "
428 "multiple services are available for a channel."),
429 .list = profile_class_svfilter_list,
430 .off = offsetof(profile_t, pro_svfilter),
431 .opts = PO_SORTKEY | PO_ADVANCED,
432 .def.i = PROFILE_SVF_NONE,
433 .group = 1
434 },
435 { }
436 }
437 };
438
439 /*
440 *
441 */
442 const char *
profile_get_name(profile_t * pro)443 profile_get_name(profile_t *pro)
444 {
445 if (pro->pro_name && *pro->pro_name) return pro->pro_name;
446 return "";
447 }
448
449 /*
450 *
451 */
452 static profile_t *
profile_find_by_name2(const char * name,const char * alt,int all)453 profile_find_by_name2(const char *name, const char *alt, int all)
454 {
455 profile_t *pro;
456
457 lock_assert(&global_lock);
458
459 if (!name && alt) {
460 name = alt;
461 alt = NULL;
462 }
463
464 if (!name)
465 return profile_default;
466
467 TAILQ_FOREACH(pro, &profiles, pro_link) {
468 if ((all || pro->pro_enabled) && !strcmp(profile_get_name(pro), name))
469 return pro;
470 }
471
472 if (alt) {
473 TAILQ_FOREACH(pro, &profiles, pro_link) {
474 if ((all || pro->pro_enabled) && !strcmp(profile_get_name(pro), alt))
475 return pro;
476 }
477 }
478
479 return profile_default;
480 }
481
482 /*
483 *
484 */
485 profile_t *
profile_find_by_name(const char * name,const char * alt)486 profile_find_by_name(const char *name, const char *alt)
487 {
488 return profile_find_by_name2(name, alt, 0);
489 }
490
491 /*
492 *
493 */
494 int
profile_verify(profile_t * pro,int sflags)495 profile_verify(profile_t *pro, int sflags)
496 {
497 if (!pro)
498 return 0;
499 if (!pro->pro_enabled)
500 return 0;
501 if ((sflags & SUBSCRIPTION_HTSP) != 0 && !pro->pro_work)
502 return 0;
503 if ((sflags & SUBSCRIPTION_HTSP) == 0 && !pro->pro_open)
504 return 0;
505 sflags &= pro->pro_sflags;
506 sflags &= SUBSCRIPTION_PACKET|SUBSCRIPTION_MPEGTS;
507 return sflags ? 1 : 0;
508 }
509
510 /*
511 *
512 */
513 profile_t *
profile_find_by_list(htsmsg_t * uuids,const char * name,const char * alt,int sflags)514 profile_find_by_list
515 (htsmsg_t *uuids, const char *name, const char *alt, int sflags)
516 {
517 profile_t *pro, *res = NULL;
518 htsmsg_field_t *f;
519 const char *uuid, *uuid2;
520 char ubuf[UUID_HEX_SIZE];
521
522 pro = profile_find_by_uuid(name);
523 if (!pro)
524 pro = profile_find_by_name(name, alt);
525 if (!profile_verify(pro, sflags))
526 pro = NULL;
527 if (uuids) {
528 uuid = pro ? idnode_uuid_as_str(&pro->pro_id, ubuf) : "";
529 HTSMSG_FOREACH(f, uuids) {
530 uuid2 = htsmsg_field_get_str(f) ?: "";
531 if (strcmp(uuid, uuid2) == 0 && profile_verify(pro, sflags))
532 return pro;
533 if (!res) {
534 res = profile_find_by_uuid(uuid2);
535 if (!profile_verify(res, sflags))
536 res = NULL;
537 }
538 }
539 } else {
540 res = pro;
541 }
542 if (!res) {
543 res = profile_find_by_name((sflags & SUBSCRIPTION_HTSP) ? "htsp" : NULL, NULL);
544 if (!profile_verify(res, sflags))
545 tvherror(LS_PROFILE, "unable to select a working profile (asked '%s' alt '%s')", name, alt);
546 }
547 return res;
548 }
549
550 /*
551 *
552 */
553 char *
profile_validate_name(const char * name)554 profile_validate_name(const char *name)
555 {
556 profile_t *pro;
557
558 lock_assert(&global_lock);
559
560 TAILQ_FOREACH(pro, &profiles, pro_link) {
561 if (name && !strcmp(profile_get_name(pro), name))
562 return strdup(name);
563 }
564
565 if (profile_default)
566 return strdup(profile_get_name(profile_default));
567
568 return NULL;
569 }
570
571 /*
572 *
573 */
574 htsmsg_t *
profile_class_get_list(void * o,const char * lang)575 profile_class_get_list(void *o, const char *lang)
576 {
577 htsmsg_t *m = htsmsg_create_map();
578 htsmsg_t *p = htsmsg_create_map();
579 htsmsg_add_str(m, "type", "api");
580 htsmsg_add_str(m, "uri", "profile/list");
581 htsmsg_add_str(m, "event", "profile");
582 htsmsg_add_u32(p, "all", 1);
583 htsmsg_add_msg(m, "params", p);
584 return m;
585 }
586
587 /*
588 *
589 */
590 void
profile_get_htsp_list(htsmsg_t * array,htsmsg_t * filter)591 profile_get_htsp_list(htsmsg_t *array, htsmsg_t *filter)
592 {
593 profile_t *pro;
594 htsmsg_t *m;
595 htsmsg_field_t *f;
596 const char *uuid, *s;
597 char ubuf[UUID_HEX_SIZE];
598
599 TAILQ_FOREACH(pro, &profiles, pro_link) {
600 if (!pro->pro_work)
601 continue;
602 uuid = idnode_uuid_as_str(&pro->pro_id, ubuf);
603 if (filter) {
604 HTSMSG_FOREACH(f, filter) {
605 if (!(s = htsmsg_field_get_str(f)))
606 continue;
607 if (strcmp(s, uuid) == 0)
608 break;
609 }
610 if (f == NULL)
611 continue;
612 }
613 m = htsmsg_create_map();
614 htsmsg_add_str(m, "uuid", uuid);
615 htsmsg_add_str(m, "name", pro->pro_name ?: "");
616 htsmsg_add_str(m, "comment", pro->pro_comment ?: "");
617 htsmsg_add_msg(array, NULL, m);
618 }
619 }
620
621 /*
622 *
623 */
624 static void
profile_deliver(profile_chain_t * prch,streaming_message_t * sm)625 profile_deliver(profile_chain_t *prch, streaming_message_t *sm)
626 {
627 if (prch->prch_start_pending) {
628 profile_sharer_t *prsh = prch->prch_sharer;
629 streaming_message_t *sm2;
630 if (!prsh->prsh_start_msg) {
631 streaming_msg_free(sm);
632 return;
633 }
634 sm2 = streaming_msg_create_data(SMT_START,
635 streaming_start_copy(prsh->prsh_start_msg));
636 streaming_target_deliver(prch->prch_post_share, sm2);
637 prch->prch_start_pending = 0;
638 }
639 if (sm)
640 streaming_target_deliver(prch->prch_post_share, sm);
641 }
642
643 /*
644 *
645 */
646 static void
profile_input(void * opaque,streaming_message_t * sm)647 profile_input(void *opaque, streaming_message_t *sm)
648 {
649 profile_chain_t *prch = opaque, *prch2;
650 profile_sharer_t *prsh = prch->prch_sharer;
651
652 if (sm->sm_type == SMT_START) {
653 if (!prsh->prsh_master)
654 prsh->prsh_master = prch;
655 prch->prch_stop = 0;
656 }
657
658 if (prch == prsh->prsh_master) {
659 if (sm->sm_type == SMT_STOP) {
660 prch->prch_stop = 1;
661 /* elect new master */
662 prsh->prsh_master = NULL;
663 LIST_FOREACH(prch2, &prsh->prsh_chains, prch_sharer_link)
664 if (!prch2->prch_stop) {
665 prsh->prsh_master = prch2;
666 break;
667 }
668 if (prsh->prsh_master)
669 goto direct;
670 }
671 streaming_target_deliver(prch->prch_share, sm);
672 return;
673 }
674
675 if (sm->sm_type == SMT_STOP) {
676 prch->prch_stop = 1;
677 } else if (sm->sm_type == SMT_START) {
678 prch->prch_stop = 0;
679 prch->prch_start_pending = 1;
680 streaming_msg_free(sm);
681 sm = NULL;
682 } else if (sm->sm_type == SMT_PACKET || sm->sm_type == SMT_MPEGTS) {
683 streaming_msg_free(sm);
684 return;
685 }
686
687 direct:
688 profile_deliver(prch, sm);
689 }
690
691 static htsmsg_t *
profile_input_info(void * opaque,htsmsg_t * list)692 profile_input_info(void *opaque, htsmsg_t *list)
693 {
694 profile_chain_t *prch = opaque;
695 streaming_target_t *st = prch->prch_share;
696 htsmsg_add_str(list, NULL, "profile input");
697 st->st_ops.st_info(st->st_opaque, list);
698 st = prch->prch_post_share;
699 return st->st_ops.st_info(st->st_opaque, list);
700 }
701
702 static streaming_ops_t profile_input_ops = {
703 .st_cb = profile_input,
704 .st_info = profile_input_info
705 };
706
707 /*
708 *
709 */
710 static void
profile_sharer_deliver(profile_chain_t * prch,streaming_message_t * sm)711 profile_sharer_deliver(profile_chain_t *prch, streaming_message_t *sm)
712 {
713 if (sm->sm_type == SMT_PACKET) {
714 if (!prch->prch_ts_delta)
715 goto deliver;
716 th_pkt_t *pkt = sm->sm_data;
717 if (prch->prch_ts_delta == PTS_UNSET)
718 prch->prch_ts_delta = MAX(0, pkt->pkt_dts - 10000);
719 /*
720 * time correction here
721 */
722 if (pkt->pkt_pts >= prch->prch_ts_delta &&
723 pkt->pkt_dts >= prch->prch_ts_delta) {
724 th_pkt_t *n = pkt_copy_shallow(pkt);
725 pkt_ref_dec(pkt);
726 n->pkt_pts -= prch->prch_ts_delta;
727 n->pkt_dts -= prch->prch_ts_delta;
728 sm->sm_data = n;
729 } else {
730 streaming_msg_free(sm);
731 return;
732 }
733 }
734 deliver:
735 profile_deliver(prch, sm);
736 }
737
738 /*
739 *
740 */
741 static void
profile_sharer_input(void * opaque,streaming_message_t * sm)742 profile_sharer_input(void *opaque, streaming_message_t *sm)
743 {
744 profile_sharer_t *prsh = opaque;
745 profile_chain_t *prch, *next, *run = NULL;
746
747 if (sm->sm_type == SMT_STOP) {
748 if (prsh->prsh_start_msg)
749 streaming_start_unref(prsh->prsh_start_msg);
750 prsh->prsh_start_msg = NULL;
751 }
752 for (prch = LIST_FIRST(&prsh->prsh_chains); prch; prch = next) {
753 next = LIST_NEXT(prch, prch_sharer_link);
754 if (prch == prsh->prsh_master) {
755 if (sm->sm_type == SMT_START) {
756 if (prsh->prsh_start_msg)
757 streaming_start_unref(prsh->prsh_start_msg);
758 prsh->prsh_start_msg = streaming_start_copy(sm->sm_data);
759 }
760 if (run)
761 profile_sharer_deliver(run, streaming_msg_clone(sm));
762 run = prch;
763 continue;
764 } else if (sm->sm_type == SMT_STOP) {
765 run = prch;
766 continue;
767 }
768 if (sm->sm_type != SMT_PACKET && sm->sm_type != SMT_MPEGTS)
769 continue;
770 if (prch->prch_stop)
771 continue;
772 if (run)
773 profile_sharer_deliver(run, streaming_msg_clone(sm));
774 run = prch;
775 }
776
777 if (run)
778 profile_sharer_deliver(run, sm);
779 else
780 streaming_msg_free(sm);
781 }
782
783 static htsmsg_t *
profile_sharer_input_info(void * opaque,htsmsg_t * list)784 profile_sharer_input_info(void *opaque, htsmsg_t *list)
785 {
786 htsmsg_add_str(list, NULL, "profile sharer input");
787 return list;
788 }
789
790 static streaming_ops_t profile_sharer_input_ops = {
791 .st_cb = profile_sharer_input,
792 .st_info = profile_sharer_input_info
793 };
794
795 /*
796 *
797 */
798 static profile_sharer_t *
profile_sharer_find(profile_chain_t * prch)799 profile_sharer_find(profile_chain_t *prch)
800 {
801 profile_sharer_t *prsh = NULL;
802 profile_chain_t *prch2;
803
804 LIST_FOREACH(prch2, &profile_chains, prch_link) {
805 if (prch2->prch_id != prch->prch_id)
806 continue;
807 if (prch2 == prch)
808 continue;
809 if (prch2->prch_can_share && prch2->prch_can_share(prch2, prch)) {
810 prsh = prch2->prch_sharer;
811 break;
812 }
813 }
814 if (!prsh) {
815 prsh = calloc(1, sizeof(*prsh));
816 streaming_target_init(&prsh->prsh_input, &profile_sharer_input_ops, prsh, 0);
817 LIST_INIT(&prsh->prsh_chains);
818 }
819 return prsh;
820 }
821
822 /*
823 *
824 */
825 static int
profile_sharer_create(profile_sharer_t * prsh,profile_chain_t * prch,streaming_target_t * dst)826 profile_sharer_create(profile_sharer_t *prsh,
827 profile_chain_t *prch,
828 streaming_target_t *dst)
829 {
830 prch->prch_post_share = dst;
831 prch->prch_ts_delta = LIST_EMPTY(&prsh->prsh_chains) ? 0 : PTS_UNSET;
832 LIST_INSERT_HEAD(&prsh->prsh_chains, prch, prch_sharer_link);
833 prch->prch_sharer = prsh;
834 if (!prsh->prsh_master)
835 prsh->prsh_master = prch;
836 return 0;
837 }
838
839 /*
840 *
841 */
842 static void
profile_sharer_destroy(profile_chain_t * prch)843 profile_sharer_destroy(profile_chain_t *prch)
844 {
845 profile_sharer_t *prsh = prch->prch_sharer;
846
847 if (prsh == NULL)
848 return;
849 LIST_REMOVE(prch, prch_sharer_link);
850 prch->prch_sharer = NULL;
851 prch->prch_post_share = NULL;
852 if (LIST_EMPTY(&prsh->prsh_chains)) {
853 if (prsh->prsh_tsfix)
854 tsfix_destroy(prsh->prsh_tsfix);
855 #if ENABLE_LIBAV
856 if (prsh->prsh_transcoder)
857 transcoder_destroy(prsh->prsh_transcoder);
858 #endif
859 if (prsh->prsh_start_msg)
860 streaming_start_unref(prsh->prsh_start_msg);
861 free(prsh);
862 }
863 }
864
865 /*
866 *
867 */
868 void
profile_chain_init(profile_chain_t * prch,profile_t * pro,void * id,int queue)869 profile_chain_init(profile_chain_t *prch, profile_t *pro, void *id, int queue)
870 {
871 memset(prch, 0, sizeof(*prch));
872 if (pro)
873 profile_grab(pro);
874 prch->prch_pro = pro;
875 prch->prch_id = id;
876 if (queue) {
877 streaming_queue_init(&prch->prch_sq, 0, 0);
878 prch->prch_sq_used = 1;
879 }
880 LIST_INSERT_HEAD(&profile_chains, prch, prch_link);
881 prch->prch_linked = 1;
882 prch->prch_stop = 1;
883 }
884
885 /*
886 *
887 */
888 int
profile_chain_work(profile_chain_t * prch,struct streaming_target * dst,uint32_t timeshift_period,int flags)889 profile_chain_work(profile_chain_t *prch, struct streaming_target *dst,
890 uint32_t timeshift_period, int flags)
891 {
892 profile_t *pro = prch->prch_pro;
893 if (pro && pro->pro_work)
894 return pro->pro_work(prch, dst, timeshift_period, flags);
895 return -1;
896 }
897
898 /*
899 *
900 */
901 int
profile_chain_reopen(profile_chain_t * prch,muxer_config_t * m_cfg,int flags)902 profile_chain_reopen(profile_chain_t *prch,
903 muxer_config_t *m_cfg, int flags)
904 {
905 profile_t *pro = prch->prch_pro;
906 if (pro && pro->pro_reopen)
907 return pro->pro_reopen(prch, m_cfg, flags);
908 return -1;
909 }
910
911 /*
912 *
913 */
914 int
profile_chain_open(profile_chain_t * prch,muxer_config_t * m_cfg,int flags,size_t qsize)915 profile_chain_open(profile_chain_t *prch,
916 muxer_config_t *m_cfg, int flags, size_t qsize)
917 {
918 profile_t *pro = prch->prch_pro;
919 if (pro && pro->pro_open)
920 return pro->pro_open(prch, m_cfg, flags, qsize);
921 return -1;
922 }
923
924 /*
925 *
926 */
927 int
profile_chain_raw_open(profile_chain_t * prch,void * id,size_t qsize,int muxer)928 profile_chain_raw_open(profile_chain_t *prch, void *id, size_t qsize, int muxer)
929 {
930 muxer_config_t c;
931
932 memset(prch, 0, sizeof(*prch));
933 prch->prch_id = id;
934 prch->prch_flags = SUBSCRIPTION_MPEGTS;
935 streaming_queue_init(&prch->prch_sq, SMT_PACKET, qsize);
936 prch->prch_sq_used = 1;
937 prch->prch_st = &prch->prch_sq.sq_st;
938 if (muxer) {
939 memset(&c, 0, sizeof(c));
940 c.m_type = MC_RAW;
941 prch->prch_muxer = muxer_create(&c);
942 }
943 return 0;
944 }
945
946 /*
947 *
948 */
949
950 static const int prio2weight[] = {
951 [PROFILE_SPRIO_DVR_IMPORTANT] = 525,
952 [PROFILE_SPRIO_DVR_HIGH] = 425,
953 [PROFILE_SPRIO_DVR_NORMAL] = 325,
954 [PROFILE_SPRIO_DVR_LOW] = 225,
955 [PROFILE_SPRIO_DVR_UNIMPORTANT] = 175,
956 [PROFILE_SPRIO_IMPORTANT] = 150,
957 [PROFILE_SPRIO_HIGH] = 125,
958 [PROFILE_SPRIO_NORMAL] = 100,
959 [PROFILE_SPRIO_LOW] = 75,
960 [PROFILE_SPRIO_UNIMPORTANT] = 50,
961 [PROFILE_SPRIO_NOTSET] = 0
962 };
963
profile_chain_weight(profile_chain_t * prch,int custom)964 int profile_chain_weight(profile_chain_t *prch, int custom)
965 {
966 int w, w2;
967
968 w = 100;
969 if (prch->prch_pro) {
970 if (!prch->prch_pro->pro_fprio && custom > 0)
971 return custom;
972 if (idnode_is_instance(&prch->prch_pro->pro_id, &profile_htsp_class))
973 w = 150;
974 w2 = prch->prch_pro->pro_prio;
975 if (w2 > 0 && w2 < ARRAY_SIZE(prio2weight))
976 w = prio2weight[w2];
977 } else {
978 if (custom > 0)
979 return custom;
980 }
981 return w;
982 }
983
984 /*
985 *
986 */
987 void
profile_chain_close(profile_chain_t * prch)988 profile_chain_close(profile_chain_t *prch)
989 {
990 if (prch == NULL)
991 return;
992
993 profile_sharer_destroy(prch);
994
995 #if ENABLE_TIMESHIFT
996 if (prch->prch_timeshift) {
997 timeshift_destroy(prch->prch_timeshift);
998 prch->prch_timeshift = NULL;
999 }
1000 #endif
1001 if (prch->prch_gh) {
1002 globalheaders_destroy(prch->prch_gh);
1003 prch->prch_gh = NULL;
1004 }
1005 if (prch->prch_tsfix) {
1006 tsfix_destroy(prch->prch_tsfix);
1007 prch->prch_tsfix = NULL;
1008 }
1009 if (prch->prch_muxer) {
1010 muxer_destroy(prch->prch_muxer);
1011 prch->prch_muxer = NULL;
1012 }
1013
1014 prch->prch_st = NULL;
1015
1016 if (prch->prch_sq_used) {
1017 streaming_queue_deinit(&prch->prch_sq);
1018 prch->prch_sq_used = 0;
1019 }
1020
1021 if (prch->prch_linked) {
1022 LIST_REMOVE(prch, prch_link);
1023 prch->prch_linked = 0;
1024 }
1025
1026 if (prch->prch_pro) {
1027 profile_release(prch->prch_pro);
1028 prch->prch_pro = NULL;
1029 }
1030
1031 prch->prch_id = NULL;
1032 }
1033
1034 /*
1035 * HTSP Profile Class
1036 */
1037 const idclass_t profile_htsp_class =
1038 {
1039 .ic_super = &profile_class,
1040 .ic_class = "profile-htsp",
1041 .ic_caption = N_("HTSP Stream Profile"),
1042 .ic_properties = (const property_t[]){
1043 /* Ready for future extensions */
1044 { }
1045 }
1046 };
1047
1048 static int
profile_htsp_work(profile_chain_t * prch,streaming_target_t * dst,uint32_t timeshift_period,int flags)1049 profile_htsp_work(profile_chain_t *prch,
1050 streaming_target_t *dst,
1051 uint32_t timeshift_period, int flags)
1052 {
1053 profile_sharer_t *prsh;
1054
1055 prsh = profile_sharer_find(prch);
1056 if (!prsh)
1057 goto fail;
1058
1059 #if ENABLE_TIMESHIFT
1060 if (timeshift_period > 0)
1061 dst = prch->prch_timeshift = timeshift_create(dst, timeshift_period);
1062 #endif
1063
1064 dst = prch->prch_gh = globalheaders_create(dst);
1065
1066 if (profile_sharer_create(prsh, prch, dst))
1067 goto fail;
1068
1069 if (!prsh->prsh_tsfix)
1070 prsh->prsh_tsfix = tsfix_create(&prsh->prsh_input);
1071
1072 prch->prch_share = prsh->prsh_tsfix;
1073 prch->prch_flags = SUBSCRIPTION_PACKET;
1074 streaming_target_init(&prch->prch_input, &profile_input_ops, prch, 0);
1075 prch->prch_st = &prch->prch_input;
1076 return 0;
1077
1078 fail:
1079 profile_chain_close(prch);
1080 return -1;
1081 }
1082
1083 static profile_t *
profile_htsp_builder(void)1084 profile_htsp_builder(void)
1085 {
1086 profile_t *pro = calloc(1, sizeof(*pro));
1087 pro->pro_sflags = SUBSCRIPTION_PACKET;
1088 pro->pro_work = profile_htsp_work;
1089 return pro;
1090 }
1091
1092 /*
1093 * MPEG-TS passthrough muxer
1094 */
1095 typedef struct profile_mpegts {
1096 profile_t;
1097 int pro_rewrite_pmt;
1098 int pro_rewrite_pat;
1099 int pro_rewrite_sdt;
1100 int pro_rewrite_eit;
1101 } profile_mpegts_t;
1102
1103 const idclass_t profile_mpegts_pass_class =
1104 {
1105 .ic_super = &profile_class,
1106 .ic_class = "profile-mpegts",
1107 .ic_caption = N_("MPEG-TS Pass-thru/built-in"),
1108 .ic_groups = (const property_group_t[]) {
1109 {
1110 .name = N_("Configuration"),
1111 .number = 1,
1112 },
1113 {
1114 .name = N_("Rewrite MPEG-TS SI tables"),
1115 .number = 2,
1116 },
1117 {}
1118 },
1119 .ic_properties = (const property_t[]){
1120 {
1121 .type = PT_BOOL,
1122 .id = "rewrite_pmt",
1123 .name = N_("Rewrite PMT"),
1124 .desc = N_("Rewrite PMT (Program Map Table) packets to only "
1125 "include information about the currently-streamed "
1126 "service."),
1127 .off = offsetof(profile_mpegts_t, pro_rewrite_pmt),
1128 .opts = PO_EXPERT,
1129 .def.i = 1,
1130 .group = 2
1131 },
1132 {
1133 .type = PT_BOOL,
1134 .id = "rewrite_pat",
1135 .name = N_("Rewrite PAT"),
1136 .desc = N_("Rewrite PAT (Program Association Table) packets "
1137 "to only include information about the currently-"
1138 "streamed service."),
1139 .off = offsetof(profile_mpegts_t, pro_rewrite_pat),
1140 .opts = PO_EXPERT,
1141 .def.i = 1,
1142 .group = 2
1143 },
1144 {
1145 .type = PT_BOOL,
1146 .id = "rewrite_sdt",
1147 .name = N_("Rewrite SDT"),
1148 .desc = N_("Rewrite SDT (Service Description Table) packets "
1149 "to only include information about the currently-"
1150 "streamed service."),
1151 .off = offsetof(profile_mpegts_t, pro_rewrite_sdt),
1152 .opts = PO_EXPERT,
1153 .def.i = 1,
1154 .group = 2
1155 },
1156 {
1157 .type = PT_BOOL,
1158 .id = "rewrite_eit",
1159 .name = N_("Rewrite EIT"),
1160 .desc = N_("Rewrite EIT (Event Information Table) packets "
1161 "to only include information about the currently-"
1162 "streamed service."),
1163 .off = offsetof(profile_mpegts_t, pro_rewrite_eit),
1164 .opts = PO_EXPERT,
1165 .def.i = 1,
1166 .group = 2
1167 },
1168 { }
1169 }
1170 };
1171
1172 static int
profile_mpegts_pass_reopen(profile_chain_t * prch,muxer_config_t * m_cfg,int flags)1173 profile_mpegts_pass_reopen(profile_chain_t *prch,
1174 muxer_config_t *m_cfg, int flags)
1175 {
1176 profile_mpegts_t *pro = (profile_mpegts_t *)prch->prch_pro;
1177 muxer_config_t c;
1178
1179 if (m_cfg)
1180 c = *m_cfg; /* do not alter the original parameter */
1181 else
1182 memset(&c, 0, sizeof(c));
1183 if (c.m_type != MC_RAW)
1184 c.m_type = MC_PASS;
1185 c.m_rewrite_pat = pro->pro_rewrite_pat;
1186 c.m_rewrite_pmt = pro->pro_rewrite_pmt;
1187 c.m_rewrite_sdt = pro->pro_rewrite_sdt;
1188 c.m_rewrite_eit = pro->pro_rewrite_eit;
1189
1190 assert(!prch->prch_muxer);
1191 prch->prch_muxer = muxer_create(&c);
1192 return 0;
1193 }
1194
1195 static int
profile_mpegts_pass_open(profile_chain_t * prch,muxer_config_t * m_cfg,int flags,size_t qsize)1196 profile_mpegts_pass_open(profile_chain_t *prch,
1197 muxer_config_t *m_cfg, int flags, size_t qsize)
1198 {
1199 prch->prch_flags = SUBSCRIPTION_MPEGTS;
1200
1201 prch->prch_sq.sq_st.st_reject_filter = SMT_PACKET;
1202 prch->prch_sq.sq_maxsize = qsize;
1203
1204 prch->prch_st = &prch->prch_sq.sq_st;
1205
1206 profile_mpegts_pass_reopen(prch, m_cfg, flags);
1207 return 0;
1208 }
1209
1210 static profile_t *
profile_mpegts_pass_builder(void)1211 profile_mpegts_pass_builder(void)
1212 {
1213 profile_mpegts_t *pro = calloc(1, sizeof(*pro));
1214 pro->pro_sflags = SUBSCRIPTION_MPEGTS;
1215 pro->pro_reopen = profile_mpegts_pass_reopen;
1216 pro->pro_open = profile_mpegts_pass_open;
1217 return (profile_t *)pro;
1218 }
1219
1220 /*
1221 * Matroska muxer
1222 */
1223 typedef struct profile_matroska {
1224 profile_t;
1225 int pro_webm;
1226 } profile_matroska_t;
1227
1228 const idclass_t profile_matroska_class =
1229 {
1230 .ic_super = &profile_class,
1231 .ic_class = "profile-matroska",
1232 .ic_caption = N_("Matroska (mkv)/built-in"),
1233 .ic_groups = (const property_group_t[]) {
1234 {
1235 .name = N_("Configuration"),
1236 .number = 1,
1237 },
1238 {
1239 .name = N_("Matroska specific"),
1240 .number = 2,
1241 },
1242 {}
1243 },
1244 .ic_properties = (const property_t[]){
1245 {
1246 .type = PT_BOOL,
1247 .id = "webm",
1248 .name = N_("WEBM"),
1249 .desc = N_("Use WEBM format."),
1250 .off = offsetof(profile_matroska_t, pro_webm),
1251 .opts = PO_ADVANCED,
1252 .def.i = 0,
1253 .group = 2
1254 },
1255 { }
1256 }
1257 };
1258
1259 static int
profile_matroska_reopen(profile_chain_t * prch,muxer_config_t * m_cfg,int flags)1260 profile_matroska_reopen(profile_chain_t *prch,
1261 muxer_config_t *m_cfg, int flags)
1262 {
1263 profile_matroska_t *pro = (profile_matroska_t *)prch->prch_pro;
1264 muxer_config_t c;
1265
1266 if (m_cfg)
1267 c = *m_cfg; /* do not alter the original parameter */
1268 else
1269 memset(&c, 0, sizeof(c));
1270 if (c.m_type != MC_WEBM)
1271 c.m_type = MC_MATROSKA;
1272 if (pro->pro_webm)
1273 c.m_type = MC_WEBM;
1274
1275 assert(!prch->prch_muxer);
1276 prch->prch_muxer = muxer_create(&c);
1277 return 0;
1278 }
1279
1280 static int
profile_matroska_open(profile_chain_t * prch,muxer_config_t * m_cfg,int flags,size_t qsize)1281 profile_matroska_open(profile_chain_t *prch,
1282 muxer_config_t *m_cfg, int flags, size_t qsize)
1283 {
1284 streaming_target_t *dst;
1285
1286 prch->prch_flags = SUBSCRIPTION_PACKET;
1287 prch->prch_sq.sq_maxsize = qsize;
1288
1289 dst = prch->prch_gh = globalheaders_create(&prch->prch_sq.sq_st);
1290 dst = prch->prch_tsfix = tsfix_create(dst);
1291 prch->prch_st = dst;
1292
1293 profile_matroska_reopen(prch, m_cfg, flags);
1294
1295 return 0;
1296 }
1297
1298 static profile_t *
profile_matroska_builder(void)1299 profile_matroska_builder(void)
1300 {
1301 profile_matroska_t *pro = calloc(1, sizeof(*pro));
1302 pro->pro_sflags = SUBSCRIPTION_PACKET;
1303 pro->pro_reopen = profile_matroska_reopen;
1304 pro->pro_open = profile_matroska_open;
1305 return (profile_t *)pro;
1306 }
1307
1308
1309 /*
1310 * Audioes Muxer
1311 */
1312 typedef struct profile_audio {
1313 profile_t;
1314 int pro_mc;
1315 int pro_index;
1316 } profile_audio_t;
1317
1318 static htsmsg_t *
profile_class_mc_audio_list(void * o,const char * lang)1319 profile_class_mc_audio_list ( void *o, const char *lang )
1320 {
1321 static const struct strtab tab[] = {
1322 { N_("Any"), MC_UNKNOWN },
1323 { N_("MPEG-2 audio"), MC_MPEG2AUDIO, },
1324 { N_("AC3 audio"), MC_AC3, },
1325 { N_("AAC audio"), MC_AAC },
1326 { N_("MP4 audio"), MC_MP4A },
1327 { N_("Vorbis audio"), MC_VORBIS },
1328 };
1329 return strtab2htsmsg(tab, 1, lang);
1330 }
1331
1332 const idclass_t profile_audio_class =
1333 {
1334 .ic_super = &profile_class,
1335 .ic_class = "profile-audio",
1336 .ic_caption = N_("Audio stream"),
1337 .ic_properties = (const property_t[]){
1338 {
1339 .type = PT_INT,
1340 .id = "type",
1341 .name = N_("Audio type"),
1342 .desc = N_("Pick the stream with given audio type only."),
1343 .off = offsetof(profile_audio_t, pro_mc),
1344 .list = profile_class_mc_audio_list,
1345 .group = 1
1346 },
1347 {
1348 .type = PT_INT,
1349 .id = "index",
1350 .name = N_("Stream index"),
1351 .desc = N_("Stream index (starts with zero)."),
1352 .off = offsetof(profile_audio_t, pro_index),
1353 .group = 1
1354 },
1355 { }
1356 }
1357 };
1358
1359
1360 static int
profile_audio_reopen(profile_chain_t * prch,muxer_config_t * m_cfg,int flags)1361 profile_audio_reopen(profile_chain_t *prch,
1362 muxer_config_t *m_cfg, int flags)
1363 {
1364 muxer_config_t c;
1365 profile_audio_t *pro = (profile_audio_t *)prch->prch_pro;
1366
1367 if (m_cfg)
1368 c = *m_cfg; /* do not alter the original parameter */
1369 else
1370 memset(&c, 0, sizeof(c));
1371 c.m_type = pro->pro_mc != MC_UNKNOWN ? pro->pro_mc : MC_MPEG2AUDIO;
1372 c.m_force_type = pro->pro_mc;
1373 c.m_index = pro->pro_index;
1374
1375 assert(!prch->prch_muxer);
1376 prch->prch_muxer = muxer_create(&c);
1377 return 0;
1378 }
1379
1380 static int
profile_audio_open(profile_chain_t * prch,muxer_config_t * m_cfg,int flags,size_t qsize)1381 profile_audio_open(profile_chain_t *prch,
1382 muxer_config_t *m_cfg, int flags, size_t qsize)
1383 {
1384 int r;
1385
1386 prch->prch_flags = SUBSCRIPTION_PACKET;
1387 prch->prch_sq.sq_maxsize = qsize;
1388
1389 r = profile_htsp_work(prch, &prch->prch_sq.sq_st, 0, 0);
1390 if (r) {
1391 profile_chain_close(prch);
1392 return r;
1393 }
1394
1395 profile_audio_reopen(prch, m_cfg, flags);
1396 return 0;
1397 }
1398
1399 static profile_t *
profile_audio_builder(void)1400 profile_audio_builder(void)
1401 {
1402 profile_audio_t *pro = calloc(1, sizeof(*pro));
1403 pro->pro_sflags = SUBSCRIPTION_PACKET;
1404 pro->pro_reopen = profile_audio_reopen;
1405 pro->pro_open = profile_audio_open;
1406 return (profile_t *)pro;
1407 }
1408
1409
1410 #if ENABLE_LIBAV
1411
1412 /*
1413 * LibAV/MPEG-TS muxer
1414 */
1415 typedef struct profile_libav_mpegts {
1416 profile_t;
1417 } profile_libav_mpegts_t;
1418
1419 const idclass_t profile_libav_mpegts_class =
1420 {
1421 .ic_super = &profile_class,
1422 .ic_class = "profile-libav-mpegts",
1423 .ic_caption = N_("MPEG-TS/av-lib"),
1424 .ic_properties = (const property_t[]){
1425 { }
1426 }
1427 };
1428
1429 static int
profile_libav_mpegts_reopen(profile_chain_t * prch,muxer_config_t * m_cfg,int flags)1430 profile_libav_mpegts_reopen(profile_chain_t *prch,
1431 muxer_config_t *m_cfg, int flags)
1432 {
1433 muxer_config_t c;
1434
1435 if (m_cfg)
1436 c = *m_cfg; /* do not alter the original parameter */
1437 else
1438 memset(&c, 0, sizeof(c));
1439 c.m_type = MC_MPEGTS;
1440
1441 assert(!prch->prch_muxer);
1442 prch->prch_muxer = muxer_create(&c);
1443 return 0;
1444 }
1445
1446 static int
profile_libav_mpegts_open(profile_chain_t * prch,muxer_config_t * m_cfg,int flags,size_t qsize)1447 profile_libav_mpegts_open(profile_chain_t *prch,
1448 muxer_config_t *m_cfg, int flags, size_t qsize)
1449 {
1450 int r;
1451
1452 prch->prch_flags = SUBSCRIPTION_PACKET;
1453 prch->prch_sq.sq_maxsize = qsize;
1454
1455 r = profile_htsp_work(prch, &prch->prch_sq.sq_st, 0, 0);
1456 if (r) {
1457 profile_chain_close(prch);
1458 return r;
1459 }
1460
1461 profile_libav_mpegts_reopen(prch, m_cfg, flags);
1462 return 0;
1463 }
1464
1465 static profile_t *
profile_libav_mpegts_builder(void)1466 profile_libav_mpegts_builder(void)
1467 {
1468 profile_libav_mpegts_t *pro = calloc(1, sizeof(*pro));
1469 pro->pro_sflags = SUBSCRIPTION_PACKET;
1470 pro->pro_reopen = profile_libav_mpegts_reopen;
1471 pro->pro_open = profile_libav_mpegts_open;
1472 return (profile_t *)pro;
1473 }
1474
1475 /*
1476 * LibAV/Matroska muxer
1477 */
1478 typedef struct profile_libav_matroska {
1479 profile_t;
1480 int pro_webm;
1481 } profile_libav_matroska_t;
1482
1483 const idclass_t profile_libav_matroska_class =
1484 {
1485 .ic_super = &profile_class,
1486 .ic_class = "profile-libav-matroska",
1487 .ic_caption = N_("Matroska/av-lib"),
1488 .ic_groups = (const property_group_t[]) {
1489 {
1490 .name = N_("Configuration"),
1491 .number = 1,
1492 },
1493 {
1494 .name = N_("Matroska specific"),
1495 .number = 2,
1496 },
1497 {}
1498 },
1499 .ic_properties = (const property_t[]){
1500 {
1501 .type = PT_BOOL,
1502 .id = "webm",
1503 .name = N_("WEBM"),
1504 .desc = N_("Use WEBM format."),
1505 .off = offsetof(profile_libav_matroska_t, pro_webm),
1506 .opts = PO_ADVANCED,
1507 .def.i = 0,
1508 .group = 2
1509 },
1510 { }
1511 }
1512 };
1513
1514 static int
profile_libav_matroska_reopen(profile_chain_t * prch,muxer_config_t * m_cfg,int flags)1515 profile_libav_matroska_reopen(profile_chain_t *prch,
1516 muxer_config_t *m_cfg, int flags)
1517 {
1518 profile_libav_matroska_t *pro = (profile_libav_matroska_t *)prch->prch_pro;
1519 muxer_config_t c;
1520
1521 if (m_cfg)
1522 c = *m_cfg; /* do not alter the original parameter */
1523 else
1524 memset(&c, 0, sizeof(c));
1525 if (c.m_type != MC_AVWEBM)
1526 c.m_type = MC_AVMATROSKA;
1527 if (pro->pro_webm)
1528 c.m_type = MC_AVWEBM;
1529
1530 assert(!prch->prch_muxer);
1531 prch->prch_muxer = muxer_create(&c);
1532 return 0;
1533 }
1534
1535 static int
profile_libav_matroska_open(profile_chain_t * prch,muxer_config_t * m_cfg,int flags,size_t qsize)1536 profile_libav_matroska_open(profile_chain_t *prch,
1537 muxer_config_t *m_cfg, int flags, size_t qsize)
1538 {
1539 int r;
1540
1541 prch->prch_flags = SUBSCRIPTION_PACKET;
1542 prch->prch_sq.sq_maxsize = qsize;
1543
1544 r = profile_htsp_work(prch, &prch->prch_sq.sq_st, 0, 0);
1545 if (r) {
1546 profile_chain_close(prch);
1547 return r;
1548 }
1549
1550 profile_libav_matroska_reopen(prch, m_cfg, flags);
1551
1552 return 0;
1553 }
1554
1555 static profile_t *
profile_libav_matroska_builder(void)1556 profile_libav_matroska_builder(void)
1557 {
1558 profile_libav_matroska_t *pro = calloc(1, sizeof(*pro));
1559 pro->pro_sflags = SUBSCRIPTION_PACKET;
1560 pro->pro_reopen = profile_libav_matroska_reopen;
1561 pro->pro_open = profile_libav_matroska_open;
1562 return (profile_t *)pro;
1563 }
1564
1565 /*
1566 * LibAV/MP4 muxer
1567 */
1568 typedef struct profile_libav_mp4 {
1569 profile_t;
1570 } profile_libav_mp4_t;
1571
1572 const idclass_t profile_libav_mp4_class =
1573 {
1574 .ic_super = &profile_class,
1575 .ic_class = "profile-libav-mp4",
1576 .ic_caption = N_("MP4/av-lib"),
1577 };
1578
1579 static int
profile_libav_mp4_reopen(profile_chain_t * prch,muxer_config_t * m_cfg,int flags)1580 profile_libav_mp4_reopen(profile_chain_t *prch,
1581 muxer_config_t *m_cfg, int flags)
1582 {
1583 muxer_config_t c;
1584
1585 if (m_cfg)
1586 c = *m_cfg; /* do not alter the original parameter */
1587 else
1588 memset(&c, 0, sizeof(c));
1589 if (c.m_type != MC_AVMP4)
1590 c.m_type = MC_AVMP4;
1591
1592 assert(!prch->prch_muxer);
1593 prch->prch_muxer = muxer_create(&c);
1594 return 0;
1595 }
1596
1597 static int
profile_libav_mp4_open(profile_chain_t * prch,muxer_config_t * m_cfg,int flags,size_t qsize)1598 profile_libav_mp4_open(profile_chain_t *prch,
1599 muxer_config_t *m_cfg, int flags, size_t qsize)
1600 {
1601 int r;
1602
1603 prch->prch_flags = SUBSCRIPTION_PACKET;
1604 prch->prch_sq.sq_maxsize = qsize;
1605
1606 r = profile_htsp_work(prch, &prch->prch_sq.sq_st, 0, 0);
1607 if (r) {
1608 profile_chain_close(prch);
1609 return r;
1610 }
1611
1612 profile_libav_mp4_reopen(prch, m_cfg, flags);
1613
1614 return 0;
1615 }
1616
1617 static profile_t *
profile_libav_mp4_builder(void)1618 profile_libav_mp4_builder(void)
1619 {
1620 profile_libav_mp4_t *pro = calloc(1, sizeof(*pro));
1621 pro->pro_sflags = SUBSCRIPTION_PACKET;
1622 pro->pro_reopen = profile_libav_mp4_reopen;
1623 pro->pro_open = profile_libav_mp4_open;
1624 return (profile_t *)pro;
1625 }
1626
1627 /*
1628 * Transcoding + packet-like muxers
1629 */
1630
1631 static int profile_transcode_experimental_codecs = 1;
1632
1633 typedef struct profile_transcode {
1634 profile_t;
1635 int pro_mc;
1636 uint32_t pro_resolution;
1637 uint32_t pro_channels;
1638 uint32_t pro_vbitrate;
1639 uint32_t pro_abitrate;
1640 char *pro_language;
1641 char *pro_vcodec;
1642 char *pro_vcodec_preset;
1643 char *pro_acodec;
1644 char *pro_scodec;
1645 char *pro_src_vcodec;
1646 } profile_transcode_t;
1647
1648
1649 static htsmsg_t *
profile_class_src_vcodec_list(void * o,const char * lang)1650 profile_class_src_vcodec_list ( void *o, const char *lang )
1651 {
1652 static const struct strtab_str tab[] = {
1653 { N_("Any"), "" },
1654 { "MPEG2VIDEO", "MPEG2VIDEO" },
1655 { "H264", "H264" },
1656 { "VP8", "VP8" },
1657 { "HEVC", "HEVC" },
1658 { "VP9", "VP9" },
1659 };
1660 return strtab2htsmsg_str(tab, 1, lang);
1661 }
1662
1663 static htsmsg_t *
profile_class_mc_list(void * o,const char * lang)1664 profile_class_mc_list ( void *o, const char *lang )
1665 {
1666 static const struct strtab tab[] = {
1667 { N_("Not set"), MC_UNKNOWN },
1668 { N_("Matroska (mkv)/built-in"), MC_MATROSKA, },
1669 { N_("WEBM/built-in"), MC_WEBM, },
1670 { N_("MPEG-TS/av-lib"), MC_MPEGTS },
1671 { N_("MPEG-PS (DVD)/av-lib"), MC_MPEGPS },
1672 { N_("Raw Audio Stream"), MC_MPEG2AUDIO },
1673 { N_("Matroska (mkv)/av-lib"), MC_AVMATROSKA },
1674 { N_("WEBM/av-lib"), MC_AVWEBM },
1675 { N_("MP4/av-lib"), MC_AVMP4 },
1676 };
1677 return strtab2htsmsg(tab, 1, lang);
1678 }
1679
1680 static htsmsg_t *
profile_class_channels_list(void * o,const char * lang)1681 profile_class_channels_list ( void *o, const char *lang )
1682 {
1683 static const struct strtab tab[] = {
1684 { N_("Copy layout"), 0 },
1685 { N_("Mono"), 1 },
1686 { N_("Stereo"), 2 },
1687 { N_("Surround (2 front, rear mono)"), 3 },
1688 { N_("Quad (4.0)"), 4 },
1689 { N_("5.0"), 5 },
1690 { N_("5.1"), 6 },
1691 { N_("6.1"), 7 },
1692 { N_("7.1"), 8 }
1693 };
1694 return strtab2htsmsg(tab, 1, lang);
1695 }
1696
1697 static htsmsg_t *
profile_class_language_list(void * o,const char * lang)1698 profile_class_language_list(void *o, const char *lang)
1699 {
1700 htsmsg_t *l = htsmsg_create_list();
1701 const lang_code_t *lc = lang_codes;
1702 char buf[128];
1703
1704 while (lc->code2b) {
1705 htsmsg_t *e;
1706 if (!strcmp(lc->code2b, "und")) {
1707 e = htsmsg_create_key_val("", tvh_gettext_lang(lang, N_("Use original")));
1708 } else {
1709 snprintf(buf, sizeof(buf), "%s (%s)", lc->desc, lc->code2b);
1710 buf[sizeof(buf)-1] = '\0';
1711 e = htsmsg_create_key_val(lc->code2b, buf);
1712 }
1713 htsmsg_add_msg(l, NULL, e);
1714 lc++;
1715 }
1716 return l;
1717 }
1718
1719 static inline int
profile_class_check_sct(htsmsg_t * c,int sct)1720 profile_class_check_sct(htsmsg_t *c, int sct)
1721 {
1722 htsmsg_field_t *f;
1723 int64_t x;
1724 HTSMSG_FOREACH(f, c)
1725 if (!htsmsg_field_get_s64(f, &x))
1726 if (x == sct)
1727 return 1;
1728 return 0;
1729 }
1730
1731 static htsmsg_t *
profile_class_codec_list(int (* check)(int sct),const char * lang)1732 profile_class_codec_list(int (*check)(int sct), const char *lang)
1733 {
1734 htsmsg_t *l = htsmsg_create_list(), *e, *c, *m;
1735 htsmsg_field_t *f;
1736 const char *s, *s2;
1737 char buf[128];
1738 int sct;
1739
1740 e = htsmsg_create_key_val("", tvh_gettext_lang(lang, N_("Do not use")));
1741 htsmsg_add_msg(l, NULL, e);
1742 e = htsmsg_create_key_val("copy", tvh_gettext_lang(lang, N_("Copy codec type")));
1743 htsmsg_add_msg(l, NULL, e);
1744 c = transcoder_get_capabilities(profile_transcode_experimental_codecs);
1745 HTSMSG_FOREACH(f, c) {
1746 if (!(m = htsmsg_field_get_map(f)))
1747 continue;
1748 if (htsmsg_get_s32(m, "type", &sct))
1749 continue;
1750 if (!check(sct))
1751 continue;
1752 if (!(s = htsmsg_get_str(m, "name")))
1753 continue;
1754 s2 = htsmsg_get_str(m, "long_name");
1755 if (s2)
1756 snprintf(buf, sizeof(buf), "%s: %s", s, s2);
1757 else
1758 snprintf(buf, sizeof(buf), "%s", s);
1759 e = htsmsg_create_key_val(s, buf);
1760 htsmsg_add_msg(l, NULL, e);
1761 }
1762 htsmsg_destroy(c);
1763 return l;
1764 }
1765
1766 static int
profile_class_vcodec_sct_check(int sct)1767 profile_class_vcodec_sct_check(int sct)
1768 {
1769 return SCT_ISVIDEO(sct);
1770 }
1771
1772 static htsmsg_t *
profile_class_vcodec_list(void * o,const char * lang)1773 profile_class_vcodec_list(void *o, const char *lang)
1774 {
1775 return profile_class_codec_list(profile_class_vcodec_sct_check, lang);
1776 }
1777
1778 static htsmsg_t *
profile_class_vcodec_preset_list(void * o,const char * lang)1779 profile_class_vcodec_preset_list(void *o, const char *lang)
1780 {
1781 static const struct strtab_str tab[] = {
1782 {N_("ultrafast: h264 / h265") , "ultrafast" },
1783 {N_("superfast: h264 / h265") , "superfast" },
1784 {N_("veryfast: h264 / h265 / qsv(h264)") , "veryfast" },
1785 {N_("faster: h264 / h265 / qsv(h264)") , "faster" },
1786 {N_("fast: h264 / h265 / qsv(h264 / h265)") , "fast" },
1787 {N_("medium: h264 / h265 / qsv(h264 / h265)") , "medium" },
1788 {N_("slow: h264 / h265 / qsv(h264 / h265)") , "slow" },
1789 {N_("slower: h264 / h265 / qsv(h264)") , "slower" },
1790 {N_("veryslow: h264 / h265 / qsv(h264)") , "veryslow" },
1791 {N_("placebo: h264 / h265") , "placebo" },
1792 {N_("hq: nvenc(h264 / h265)") , "hq" },
1793 {N_("hp: nvenc(h264 / h265)") , "hp" },
1794 {N_("bd: nvenc(h264 / h265)") , "bd" },
1795 {N_("ll: nvenc(h264 / h265)") , "ll" },
1796 {N_("llhq: nvenc(h264 / h265)") , "llhq" },
1797 {N_("llhp: nvenc(h264 / h265)") , "llhp" },
1798 {N_("default: nvenc(h264 / h265)") , "default" }
1799 };
1800 return strtab2htsmsg_str(tab, 1, lang);
1801 }
1802
1803 static int
profile_class_acodec_sct_check(int sct)1804 profile_class_acodec_sct_check(int sct)
1805 {
1806 return SCT_ISAUDIO(sct);
1807 }
1808
1809 static htsmsg_t *
profile_class_acodec_list(void * o,const char * lang)1810 profile_class_acodec_list(void *o, const char *lang)
1811 {
1812 return profile_class_codec_list(profile_class_acodec_sct_check, lang);
1813 }
1814
1815 static int
profile_class_scodec_sct_check(int sct)1816 profile_class_scodec_sct_check(int sct)
1817 {
1818 return SCT_ISSUBTITLE(sct);
1819 }
1820
1821 static htsmsg_t *
profile_class_scodec_list(void * o,const char * lang)1822 profile_class_scodec_list(void *o, const char *lang)
1823 {
1824 return profile_class_codec_list(profile_class_scodec_sct_check, lang);
1825 }
1826
1827 const idclass_t profile_transcode_class =
1828 {
1829 .ic_super = &profile_class,
1830 .ic_class = "profile-transcode",
1831 .ic_caption = N_("Transcode/av-lib"),
1832 .ic_groups = (const property_group_t[]) {
1833 {
1834 .name = N_("Configuration"),
1835 .number = 1,
1836 },
1837 {
1838 .name = N_("Transcoding"),
1839 .number = 2,
1840 },
1841 {}
1842 },
1843 .ic_properties = (const property_t[]){
1844 {
1845 .type = PT_INT,
1846 .id = "container",
1847 .name = N_("Container"),
1848 .desc = N_("Container to use for the transcoded stream."),
1849 .off = offsetof(profile_transcode_t, pro_mc),
1850 .def.i = MC_MATROSKA,
1851 .list = profile_class_mc_list,
1852 .group = 1
1853 },
1854 {
1855 .type = PT_U32,
1856 .id = "resolution",
1857 .name = N_("Resolution (height)"),
1858 .desc = N_("Vertical resolution (height) of the output video "
1859 "stream. Horizontal resolution is adjusted "
1860 "automatically to preserve aspect ratio. When set "
1861 "to 0, the input resolution is used."),
1862 .off = offsetof(profile_transcode_t, pro_resolution),
1863 .def.u32 = 384,
1864 .group = 2
1865 },
1866 {
1867 .type = PT_U32,
1868 .id = "channels",
1869 .name = N_("Channels"),
1870 .desc = N_("Audio channel layout."),
1871 .off = offsetof(profile_transcode_t, pro_channels),
1872 .def.u32 = 2,
1873 .list = profile_class_channels_list,
1874 .opts = PO_ADVANCED,
1875 .group = 2
1876 },
1877 {
1878 .type = PT_STR,
1879 .id = "language",
1880 .name = N_("Language"),
1881 .desc = N_("Preferred audio language."),
1882 .off = offsetof(profile_transcode_t, pro_language),
1883 .list = profile_class_language_list,
1884 .opts = PO_ADVANCED,
1885 .group = 2
1886 },
1887 {
1888 .type = PT_STR,
1889 .id = "src_vcodec",
1890 .name = N_("Source video codec"),
1891 .desc = N_("Transcode video only if source video codec matches. "
1892 "\"Any\" ignores source video codec checking and "
1893 "always transcodes. If no codec match is found, "
1894 "transcoding is done using the \"copy\" codec. "
1895 "if a match is found, transcode with the "
1896 "parameters in this profile. Separate codec names "
1897 "with comma."),
1898 .off = offsetof(profile_transcode_t, pro_src_vcodec),
1899 .def.i = SCT_UNKNOWN,
1900 .list = profile_class_src_vcodec_list,
1901 .opts = PO_ADVANCED,
1902 .group = 2
1903 },
1904 {
1905 .type = PT_STR,
1906 .id = "vcodec",
1907 .name = N_("Video codec"),
1908 .desc = N_("Video codec to use for the transcode. "
1909 "\"Do not use\" will disable video output."),
1910 .off = offsetof(profile_transcode_t, pro_vcodec),
1911 .def.s = "libx264",
1912 .list = profile_class_vcodec_list,
1913 .opts = PO_ADVANCED,
1914 .group = 2
1915 },
1916 {
1917 .type = PT_STR,
1918 .id = "vcodec_preset",
1919 .name = N_("Video codec preset"),
1920 .desc = N_("Video codec preset to use for transcoding."),
1921 .off = offsetof(profile_transcode_t, pro_vcodec_preset),
1922 .def.s = "faster",
1923 .list = profile_class_vcodec_preset_list,
1924 .opts = PO_ADVANCED,
1925 .group = 2
1926 },
1927 {
1928 .type = PT_U32,
1929 .id = "vbitrate",
1930 .name = N_("Video bitrate (kb/s) (0=auto)"),
1931 .desc = N_("Bitrate to use for the transcode. See Help for "
1932 "details."),
1933 .off = offsetof(profile_transcode_t, pro_vbitrate),
1934 .opts = PO_ADVANCED,
1935 .def.u32 = 0,
1936 .group = 2
1937 },
1938 {
1939 .type = PT_STR,
1940 .id = "acodec",
1941 .name = N_("Audio codec"),
1942 .desc = N_("Audio codec to use for the transcode. \"Do not "
1943 "use\" will disable audio output."),
1944 .off = offsetof(profile_transcode_t, pro_acodec),
1945 .def.s = "libvorbis",
1946 .list = profile_class_acodec_list,
1947 .opts = PO_ADVANCED,
1948 .group = 2
1949 },
1950 {
1951 .type = PT_U32,
1952 .id = "abitrate",
1953 .name = N_("Audio bitrate (kb/s) (0=auto)"),
1954 .desc = N_("Audio bitrate to use for transcoding."),
1955 .off = offsetof(profile_transcode_t, pro_abitrate),
1956 .opts = PO_ADVANCED,
1957 .def.u32 = 0,
1958 .group = 2
1959 },
1960 {
1961 .type = PT_STR,
1962 .id = "scodec",
1963 .name = N_("Subtitle codec"),
1964 .desc = N_("Select subtitle codec to use for transcoding."),
1965 .off = offsetof(profile_transcode_t, pro_scodec),
1966 .def.s = "",
1967 .list = profile_class_scodec_list,
1968 .opts = PO_ADVANCED,
1969 .group = 2
1970 },
1971 { }
1972 }
1973 };
1974
1975 static int
profile_transcode_resolution(profile_transcode_t * pro)1976 profile_transcode_resolution(profile_transcode_t *pro)
1977 {
1978 return pro->pro_resolution == 0 ? 0 :
1979 (pro->pro_resolution >= 240 ? pro->pro_resolution : 240);
1980 }
1981
1982 static int
profile_transcode_vbitrate(profile_transcode_t * pro)1983 profile_transcode_vbitrate(profile_transcode_t *pro)
1984 {
1985 return pro->pro_vbitrate;
1986 }
1987
1988 static int
profile_transcode_abitrate(profile_transcode_t * pro)1989 profile_transcode_abitrate(profile_transcode_t *pro)
1990 {
1991 return pro->pro_abitrate;
1992 }
1993
1994 static int
profile_transcode_can_share(profile_chain_t * prch,profile_chain_t * joiner)1995 profile_transcode_can_share(profile_chain_t *prch,
1996 profile_chain_t *joiner)
1997 {
1998 profile_transcode_t *pro1 = (profile_transcode_t *)prch->prch_pro;
1999 profile_transcode_t *pro2 = (profile_transcode_t *)joiner->prch_pro;
2000 if (pro1 == pro2)
2001 return 1;
2002 if (!idnode_is_instance(&pro2->pro_id, &profile_transcode_class))
2003 return 0;
2004 /*
2005 * Do full params check here, note that profiles might differ
2006 * only in the muxer setup.
2007 */
2008 if (strcmp(pro1->pro_vcodec ?: "", pro2->pro_vcodec ?: ""))
2009 return 0;
2010 if (strcmp(pro1->pro_vcodec_preset ?: "", pro2->pro_vcodec_preset ?: ""))
2011 return 0;
2012 if (strcmp(pro1->pro_acodec ?: "", pro2->pro_acodec ?: ""))
2013 return 0;
2014 if (strcmp(pro1->pro_scodec ?: "", pro2->pro_scodec ?: ""))
2015 return 0;
2016 if (profile_transcode_resolution(pro1) != profile_transcode_resolution(pro2))
2017 return 0;
2018 if (profile_transcode_vbitrate(pro1) != profile_transcode_vbitrate(pro2))
2019 return 0;
2020 if (profile_transcode_abitrate(pro1) != profile_transcode_abitrate(pro2))
2021 return 0;
2022 if (strcmp(pro1->pro_language ?: "", pro2->pro_language ?: ""))
2023 return 0;
2024 return 1;
2025 }
2026
2027 static int
profile_transcode_work(profile_chain_t * prch,streaming_target_t * dst,uint32_t timeshift_period,int flags)2028 profile_transcode_work(profile_chain_t *prch,
2029 streaming_target_t *dst,
2030 uint32_t timeshift_period, int flags)
2031 {
2032 profile_sharer_t *prsh;
2033 profile_transcode_t *pro = (profile_transcode_t *)prch->prch_pro;
2034 transcoder_props_t props;
2035
2036 prsh = profile_sharer_find(prch);
2037 if (!prsh)
2038 goto fail;
2039
2040 prch->prch_can_share = profile_transcode_can_share;
2041
2042 memset(&props, 0, sizeof(props));
2043 strncpy(props.tp_vcodec, pro->pro_vcodec ?: "", sizeof(props.tp_vcodec)-1);
2044 strncpy(props.tp_vcodec_preset, pro->pro_vcodec_preset ?: "", sizeof(props.tp_vcodec_preset)-1);
2045 strncpy(props.tp_acodec, pro->pro_acodec ?: "", sizeof(props.tp_acodec)-1);
2046 strncpy(props.tp_scodec, pro->pro_scodec ?: "", sizeof(props.tp_scodec)-1);
2047 props.tp_resolution = profile_transcode_resolution(pro);
2048 props.tp_channels = pro->pro_channels;
2049 props.tp_vbitrate = profile_transcode_vbitrate(pro);
2050 props.tp_abitrate = profile_transcode_abitrate(pro);
2051 strncpy(props.tp_language, pro->pro_language ?: "", 3);
2052
2053 if (!pro->pro_src_vcodec) {
2054 strcpy(props.tp_src_vcodec, "");
2055 } else if(!strncasecmp("Any",pro->pro_src_vcodec,3)) {
2056 strcpy(props.tp_src_vcodec, "");
2057 } else {
2058 strncpy(props.tp_src_vcodec, pro->pro_src_vcodec ?: "", sizeof(props.tp_src_vcodec)-1);
2059 }
2060
2061 dst = prch->prch_gh = globalheaders_create(dst);
2062
2063 #if ENABLE_TIMESHIFT
2064 if (timeshift_period > 0)
2065 dst = prch->prch_timeshift = timeshift_create(dst, timeshift_period);
2066 #endif
2067 if (profile_sharer_create(prsh, prch, dst))
2068 goto fail;
2069 if (!prsh->prsh_transcoder) {
2070 assert(!prsh->prsh_tsfix);
2071 dst = prsh->prsh_transcoder = transcoder_create(&prsh->prsh_input);
2072 if (!dst)
2073 goto fail;
2074 transcoder_set_properties(dst, &props);
2075 prsh->prsh_tsfix = tsfix_create(dst);
2076 }
2077 prch->prch_share = prsh->prsh_tsfix;
2078 streaming_target_init(&prch->prch_input, &profile_input_ops, prch, 0);
2079 prch->prch_st = &prch->prch_input;
2080 return 0;
2081 fail:
2082 profile_chain_close(prch);
2083 return -1;
2084 }
2085
2086 static int
profile_transcode_mc_valid(int mc)2087 profile_transcode_mc_valid(int mc)
2088 {
2089 switch (mc) {
2090 case MC_MATROSKA:
2091 case MC_WEBM:
2092 case MC_MPEGTS:
2093 case MC_MPEGPS:
2094 case MC_MPEG2AUDIO:
2095 case MC_AC3:
2096 case MC_AAC:
2097 case MC_VORBIS:
2098 case MC_AVMATROSKA:
2099 case MC_AVMP4:
2100 return 1;
2101 default:
2102 return 0;
2103 }
2104 }
2105
2106 static int
profile_transcode_reopen(profile_chain_t * prch,muxer_config_t * m_cfg,int flags)2107 profile_transcode_reopen(profile_chain_t *prch,
2108 muxer_config_t *m_cfg, int flags)
2109 {
2110 profile_transcode_t *pro = (profile_transcode_t *)prch->prch_pro;
2111 muxer_config_t c;
2112
2113 if (m_cfg)
2114 c = *m_cfg; /* do not alter the original parameter */
2115 else
2116 memset(&c, 0, sizeof(c));
2117 if (!profile_transcode_mc_valid(c.m_type)) {
2118 c.m_type = pro->pro_mc;
2119 if (!profile_transcode_mc_valid(c.m_type))
2120 c.m_type = MC_MATROSKA;
2121 }
2122
2123 assert(!prch->prch_muxer);
2124 prch->prch_muxer = muxer_create(&c);
2125 return 0;
2126 }
2127
2128 static int
profile_transcode_open(profile_chain_t * prch,muxer_config_t * m_cfg,int flags,size_t qsize)2129 profile_transcode_open(profile_chain_t *prch,
2130 muxer_config_t *m_cfg, int flags, size_t qsize)
2131 {
2132 int r;
2133
2134 prch->prch_flags = SUBSCRIPTION_PACKET;
2135 prch->prch_sq.sq_maxsize = qsize;
2136
2137 r = profile_transcode_work(prch, &prch->prch_sq.sq_st, 0, 0);
2138 if (r) {
2139 profile_chain_close(prch);
2140 return r;
2141 }
2142
2143 profile_transcode_reopen(prch, m_cfg, flags);
2144 return 0;
2145 }
2146
2147 static void
profile_transcode_free(profile_t * _pro)2148 profile_transcode_free(profile_t *_pro)
2149 {
2150 profile_transcode_t *pro = (profile_transcode_t *)_pro;
2151 free(pro->pro_vcodec);
2152 free(pro->pro_vcodec_preset);
2153 free(pro->pro_acodec);
2154 free(pro->pro_scodec);
2155 free(pro->pro_src_vcodec);
2156 }
2157
2158 static profile_t *
profile_transcode_builder(void)2159 profile_transcode_builder(void)
2160 {
2161 profile_transcode_t *pro = calloc(1, sizeof(*pro));
2162 pro->pro_sflags = SUBSCRIPTION_PACKET;
2163 pro->pro_free = profile_transcode_free;
2164 pro->pro_work = profile_transcode_work;
2165 pro->pro_reopen = profile_transcode_reopen;
2166 pro->pro_open = profile_transcode_open;
2167 return (profile_t *)pro;
2168 }
2169
2170 #endif /* ENABLE_TRANSCODE */
2171
2172 /*
2173 * Initialize
2174 */
2175 void
profile_init(void)2176 profile_init(void)
2177 {
2178 htsmsg_t *c, *e;
2179 htsmsg_field_t *f;
2180 profile_t *pro;
2181 const char *name;
2182
2183 LIST_INIT(&profile_builders);
2184 TAILQ_INIT(&profiles);
2185 LIST_INIT(&profile_chains);
2186
2187 profile_register(&profile_mpegts_pass_class, profile_mpegts_pass_builder);
2188 profile_register(&profile_matroska_class, profile_matroska_builder);
2189 profile_register(&profile_htsp_class, profile_htsp_builder);
2190 profile_register(&profile_audio_class, profile_audio_builder);
2191 #if ENABLE_LIBAV
2192 profile_register(&profile_libav_mpegts_class, profile_libav_mpegts_builder);
2193 profile_register(&profile_libav_matroska_class, profile_libav_matroska_builder);
2194 profile_register(&profile_libav_mp4_class, profile_libav_mp4_builder);
2195 profile_transcode_experimental_codecs =
2196 getenv("TVHEADEND_LIBAV_NO_EXPERIMENTAL_CODECS") ? 0 : 1;
2197 profile_register(&profile_transcode_class, profile_transcode_builder);
2198 #endif
2199
2200 if ((c = hts_settings_load("profile")) != NULL) {
2201 HTSMSG_FOREACH(f, c) {
2202 if (!(e = htsmsg_field_get_map(f)))
2203 continue;
2204 (void)profile_create(f->hmf_name, e, 0);
2205 }
2206 htsmsg_destroy(c);
2207 }
2208
2209 name = "pass";
2210 pro = profile_find_by_name2(name, NULL, 1);
2211 if (pro == NULL || strcmp(profile_get_name(pro), name)) {
2212 htsmsg_t *conf;
2213
2214 conf = htsmsg_create_map();
2215 htsmsg_add_str (conf, "class", "profile-mpegts");
2216 htsmsg_add_bool(conf, "enabled", 1);
2217 htsmsg_add_bool(conf, "default", 1);
2218 htsmsg_add_str (conf, "name", name);
2219 htsmsg_add_str (conf, "comment", _("MPEG-TS Pass-thru"));
2220 htsmsg_add_s32 (conf, "priority", PROFILE_SPRIO_NORMAL);
2221 htsmsg_add_bool(conf, "rewrite_pmt", 1);
2222 htsmsg_add_bool(conf, "rewrite_pat", 1);
2223 htsmsg_add_bool(conf, "rewrite_sdt", 1);
2224 htsmsg_add_bool(conf, "rewrite_eit", 1);
2225 htsmsg_add_bool(conf, "shield", 1);
2226 (void)profile_create(NULL, conf, 1);
2227 htsmsg_destroy(conf);
2228 }
2229
2230 name = "matroska";
2231 pro = profile_find_by_name2(name, NULL, 1);
2232 if (pro == NULL || strcmp(profile_get_name(pro), name)) {
2233 htsmsg_t *conf;
2234
2235 conf = htsmsg_create_map();
2236 htsmsg_add_str (conf, "class", "profile-matroska");
2237 htsmsg_add_bool(conf, "enabled", 1);
2238 htsmsg_add_str (conf, "name", name);
2239 htsmsg_add_str (conf, "comment", _("Matroska"));
2240 htsmsg_add_s32 (conf, "priority", PROFILE_SPRIO_NORMAL);
2241 htsmsg_add_bool(conf, "shield", 1);
2242 (void)profile_create(NULL, conf, 1);
2243 htsmsg_destroy(conf);
2244 }
2245
2246 name = "htsp";
2247 pro = profile_find_by_name2(name, NULL, 1);
2248 if (pro == NULL || strcmp(profile_get_name(pro), name)) {
2249 htsmsg_t *conf;
2250
2251 conf = htsmsg_create_map();
2252 htsmsg_add_str (conf, "class", "profile-htsp");
2253 htsmsg_add_bool(conf, "enabled", 1);
2254 htsmsg_add_str (conf, "name", name);
2255 htsmsg_add_str (conf, "comment", _("HTSP Default Stream Settings"));
2256 htsmsg_add_s32 (conf, "priority", PROFILE_SPRIO_IMPORTANT);
2257 htsmsg_add_bool(conf, "shield", 1);
2258 (void)profile_create(NULL, conf, 1);
2259 htsmsg_destroy(conf);
2260 }
2261
2262 name = "audio";
2263 pro = profile_find_by_name2(name, NULL, 1);
2264 if (pro == NULL || strcmp(profile_get_name(pro), name)) {
2265 htsmsg_t *conf;
2266
2267 conf = htsmsg_create_map();
2268 htsmsg_add_str (conf, "class", "profile-audio");
2269 htsmsg_add_bool(conf, "enabled", 1);
2270 htsmsg_add_str (conf, "name", name);
2271 htsmsg_add_str (conf, "comment", _("Audio-only stream"));
2272 htsmsg_add_s32 (conf, "priority", PROFILE_SPRIO_NORMAL);
2273 htsmsg_add_bool(conf, "shield", 1);
2274 (void)profile_create(NULL, conf, 1);
2275 htsmsg_destroy(conf);
2276 }
2277
2278 #if ENABLE_LIBAV
2279
2280 name = "webtv-vp8-vorbis-webm";
2281 pro = profile_find_by_name2(name, NULL, 1);
2282 if (pro == NULL || strcmp(profile_get_name(pro), name)) {
2283 htsmsg_t *conf;
2284
2285 conf = htsmsg_create_map();
2286 htsmsg_add_str (conf, "class", "profile-transcode");
2287 htsmsg_add_bool(conf, "enabled", 1);
2288 htsmsg_add_str (conf, "name", name);
2289 htsmsg_add_str (conf, "comment", _("WEBTV profile VP8/Vorbis/WEBM"));
2290 htsmsg_add_s32 (conf, "priority", PROFILE_SPRIO_NORMAL);
2291 htsmsg_add_s32 (conf, "container", MC_WEBM);
2292 htsmsg_add_u32 (conf, "resolution", 384);
2293 htsmsg_add_u32 (conf, "channels", 2);
2294 htsmsg_add_str (conf, "vcodec", "libvpx");
2295 htsmsg_add_str (conf, "vcodec_preset", "faster");
2296 htsmsg_add_str (conf, "acodec", "libvorbis");
2297 htsmsg_add_bool(conf, "shield", 1);
2298 (void)profile_create(NULL, conf, 1);
2299 htsmsg_destroy(conf);
2300 }
2301 name = "webtv-h264-aac-mpegts";
2302 pro = profile_find_by_name2(name, NULL, 1);
2303 if (pro == NULL || strcmp(profile_get_name(pro), name)) {
2304 htsmsg_t *conf;
2305
2306 conf = htsmsg_create_map();
2307 htsmsg_add_str (conf, "class", "profile-transcode");
2308 htsmsg_add_bool(conf, "enabled", 1);
2309 htsmsg_add_str (conf, "name", name);
2310 htsmsg_add_str (conf, "comment", _("WEBTV profile H264/AAC/MPEG-TS"));
2311 htsmsg_add_s32 (conf, "priority", PROFILE_SPRIO_NORMAL);
2312 htsmsg_add_s32 (conf, "container", MC_MPEGTS);
2313 htsmsg_add_u32 (conf, "resolution", 384);
2314 htsmsg_add_u32 (conf, "channels", 2);
2315 htsmsg_add_str (conf, "vcodec", "libx264");
2316 htsmsg_add_str (conf, "vcodec_preset", "faster");
2317 htsmsg_add_str (conf, "acodec", "aac");
2318 htsmsg_add_bool(conf, "shield", 1);
2319 (void)profile_create(NULL, conf, 1);
2320 htsmsg_destroy(conf);
2321 }
2322 name = "webtv-h264-aac-matroska";
2323 pro = profile_find_by_name2(name, NULL, 1);
2324 if (pro == NULL || strcmp(profile_get_name(pro), name)) {
2325 htsmsg_t *conf;
2326
2327 conf = htsmsg_create_map();
2328 htsmsg_add_str (conf, "class", "profile-transcode");
2329 htsmsg_add_bool(conf, "enabled", 1);
2330 htsmsg_add_str (conf, "name", name);
2331 htsmsg_add_str (conf, "comment", _("WEBTV profile H264/AAC/Matroska"));
2332 htsmsg_add_s32 (conf, "priority", PROFILE_SPRIO_NORMAL);
2333 htsmsg_add_s32 (conf, "container", MC_MATROSKA);
2334 htsmsg_add_u32 (conf, "resolution", 384);
2335 htsmsg_add_u32 (conf, "channels", 2);
2336 htsmsg_add_str (conf, "vcodec", "libx264");
2337 htsmsg_add_str (conf, "vcodec_preset", "faster");
2338 htsmsg_add_str (conf, "acodec", "aac");
2339 htsmsg_add_bool(conf, "shield", 1);
2340 (void)profile_create(NULL, conf, 1);
2341 htsmsg_destroy(conf);
2342 }
2343 #endif
2344
2345 /* Assign the default profile if config files are corrupted */
2346 if (!profile_default) {
2347 pro = profile_find_by_name2("pass", NULL, 1);
2348 assert(pro);
2349 profile_default = pro;
2350 }
2351 }
2352
2353 void
profile_done(void)2354 profile_done(void)
2355 {
2356 profile_t *pro;
2357 profile_build_t *pb;
2358
2359 pthread_mutex_lock(&global_lock);
2360 profile_default = NULL;
2361 while ((pro = TAILQ_FIRST(&profiles)) != NULL)
2362 profile_delete(pro, 0);
2363 while ((pb = LIST_FIRST(&profile_builders)) != NULL) {
2364 LIST_REMOVE(pb, link);
2365 free(pb);
2366 }
2367 pthread_mutex_unlock(&global_lock);
2368 }
2369