#include "muxer.h"
#include "muxer/muxer_mkv.h"
#include "muxer/muxer_pass.h"
+#include "muxer/muxer_audioes.h"
#if CONFIG_LIBAV
#include "muxer/muxer_libav.h"
#endif
{ "audio/mp4", MC_AVMP4 },
{ "application/octet-stream", MC_PASS },
{ "application/octet-stream", MC_RAW },
+ { "audio/mpeg", MC_AUDIOES },
};
{ "avmatroska", MC_AVMATROSKA },
{ "avwebm", MC_AVWEBM },
{ "avmp4", MC_AVMP4 },
+ { "audioes", MC_AUDIOES },
};
{ "mka", MC_AVMATROSKA },
{ "webm", MC_AVWEBM },
{ "mp4", MC_AVMP4 },
+ { "mp2", MC_AUDIOES }, /* Or maybe ac3 or adts */
};
{ "mkv", MC_AVMATROSKA },
{ "webm", MC_AVWEBM },
{ "mp4", MC_AVMP4 },
+ { NULL, MC_AUDIOES },
};
if(!m)
m = mkv_muxer_create(m_cfg);
+ if(!m)
+ m = audioes_muxer_create(m_cfg);
+
#if CONFIG_LIBAV
if(!m)
m = lav_muxer_create(m_cfg);
MC_AVMATROSKA = 7,
MC_AVWEBM = 8,
MC_AVMP4 = 9,
+ MC_AUDIOES = 10
} muxer_container_type_t;
typedef enum {
--- /dev/null
+/*
+ * "muxer" to write mpeg audio elementary streams
+ * Copyright (C) 2013 Dave Chapman
+ *
+ * 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 <htmlui://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#include "tvheadend.h"
+#include "streaming.h"
+#include "epg.h"
+#include "channels.h"
+#include "muxer_audioes.h"
+
+typedef struct audioes_muxer {
+ muxer_t;
+
+ int error;
+
+ /* Info about the service */
+ int am_index;
+
+ /* File descriptor stuff */
+ int am_fd;
+ int am_seekable;
+ int am_error;
+
+ /* Filename is also used for logging */
+ char *am_filename;
+} audioes_muxer_t;
+
+
+/**
+ * Figure out the mimetype
+ */
+static const char*
+audioes_muxer_mime(muxer_t* m, const struct streaming_start *ss)
+{
+ int i;
+ int has_audio;
+ const streaming_start_component_t *ssc;
+
+ has_audio = 0;
+
+ for(i=0; i < ss->ss_num_components; i++) {
+ ssc = &ss->ss_components[i];
+
+ if(ssc->ssc_disabled)
+ continue;
+
+ has_audio |= SCT_ISAUDIO(ssc->ssc_type);
+ }
+
+ if(has_audio)
+ return muxer_container_type2mime(MC_AUDIOES, 0);
+ else
+ return muxer_container_type2mime(MC_UNKNOWN, 0);
+}
+
+
+/**
+ * Init the builtin mkv muxer with streams
+ */
+static int
+audioes_muxer_init(muxer_t* m, struct streaming_start *ss, const char *name)
+{
+ audioes_muxer_t *am = (audioes_muxer_t*)m;
+ const streaming_start_component_t *ssc;
+ int i;
+
+ am->am_index = -1;
+
+ for(i = 0; i < ss->ss_num_components;i++) {
+ ssc = &ss->ss_components[i];
+
+ if ((!ssc->ssc_disabled) && (SCT_ISAUDIO(ssc->ssc_type))) {
+ am->am_index = ssc->ssc_index;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+
+static int
+audioes_muxer_reconfigure(muxer_t* m, const struct streaming_start *ss)
+{
+ /* TODO: Check our stream still exists? */
+
+ return 0;
+}
+
+
+/*
+ * Open the muxer as a stream muxer (using a non-seekable socket)
+ */
+static int
+audioes_muxer_open_stream(muxer_t *m, int fd)
+{
+ audioes_muxer_t *am = (audioes_muxer_t*)m;
+
+ am->am_fd = fd;
+ am->am_seekable = 0;
+ am->am_filename = strdup("Live stream");
+
+ return 0;
+}
+
+
+/**
+ * Open the file and set the file descriptor
+ */
+static int
+audioes_muxer_open_file(muxer_t *m, const char *filename)
+{
+ int fd;
+ audioes_muxer_t *am = (audioes_muxer_t*)m;
+
+ tvhtrace(LS_AUDIOES, "Creating file \"%s\" with file permissions \"%o\"", filename, am->m_config.m_file_permissions);
+
+ fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, am->m_config.m_file_permissions);
+
+ if(fd < 0) {
+ am->am_error = errno;
+ tvherror(LS_AUDIOES, "%s: Unable to create file, open failed -- %s",
+ filename, strerror(errno));
+ am->m_errors++;
+ return -1;
+ }
+
+ /* bypass umask settings */
+ if (fchmod(fd, am->m_config.m_file_permissions))
+ tvherror(LS_AUDIOES, "%s: Unable to change permissions -- %s",
+ filename, strerror(errno));
+
+ am->am_seekable = 1;
+ am->am_fd = fd;
+ am->am_filename = strdup(filename);
+ return 0;
+}
+
+/**
+ * Write a packet to the muxer
+ */
+static int
+audioes_muxer_write_pkt(muxer_t *m, streaming_message_type_t smt, void *data)
+{
+ th_pkt_t *pkt = (th_pkt_t*)data;
+ audioes_muxer_t *am = (audioes_muxer_t*)m;
+
+ assert(smt == SMT_PACKET);
+
+ // TODO: pkt->pkt_componentindex
+ /* TODO: ^ What does this even mean? */
+
+ if(pkt->pkt_componentindex != am->am_index) {
+ pkt_ref_dec(pkt);
+ return am->error;
+ }
+
+ if(am->am_error) {
+ am->m_errors++;
+ } else if(tvh_write(am->am_fd, pktbuf_ptr(pkt->pkt_payload), pktbuf_len(pkt->pkt_payload))) {
+ am->am_error = errno;
+ tvherror(LS_AUDIOES, "%s: Write failed -- %s", am->am_filename,
+ strerror(errno));
+ /* TODO: Do some EOS handling here. Whatever that is. See muxer_pass.c:415 */
+ am->m_errors++;
+ /* TODO: A muxer_cache_update() call is still missing here. */
+ }
+
+ pkt_ref_dec(pkt);
+ return am->error;
+}
+
+
+/**
+ * NOP
+ */
+static int
+audioes_muxer_write_meta(muxer_t *m, struct epg_broadcast *eb, const char *comment)
+{
+ return 0;
+}
+
+
+
+/**
+ * Close the muxer
+ */
+static int
+audioes_muxer_close(muxer_t *m)
+{
+ audioes_muxer_t *am = (audioes_muxer_t*)m;
+
+ if ((am->am_seekable) && (close(am->am_fd))) {
+ am->am_error = errno;
+ tvherror(LS_AUDIOES, "%s: Unable to close file -- %s",
+ am->am_filename, strerror(errno));
+ am->m_errors++;
+ return -1;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Free all memory associated with the muxer
+ */
+static void
+audioes_muxer_destroy(muxer_t *m)
+{
+ audioes_muxer_t *am = (audioes_muxer_t*)m;
+
+ if(am->am_filename)
+ free(am->am_filename);
+
+ free(am);
+}
+
+
+/**
+ * Create a new builtin muxer
+ */
+muxer_t*
+audioes_muxer_create(const muxer_config_t *m_cfg)
+{
+ audioes_muxer_t *am;
+
+ if(m_cfg->m_type != MC_AUDIOES)
+ return NULL;
+
+ am = calloc(1, sizeof(audioes_muxer_t));
+ am->m_open_stream = audioes_muxer_open_stream;
+ am->m_open_file = audioes_muxer_open_file;
+ am->m_mime = audioes_muxer_mime;
+ am->m_init = audioes_muxer_init;
+ am->m_reconfigure = audioes_muxer_reconfigure;
+ am->m_write_meta = audioes_muxer_write_meta;
+ am->m_write_pkt = audioes_muxer_write_pkt;
+ am->m_close = audioes_muxer_close;
+ am->m_destroy = audioes_muxer_destroy;
+ //am->m_container = *m_cfg; //WTF DOES THIS DO?!
+
+ return (muxer_t*)am;
+}
+
--- /dev/null
+/*
+ * "muxer" to write raw audio streams
+ * Copyright (C) 2013 Dave Chapman
+ *
+ * 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 <htmlui://www.gnu.org/licenses/>.
+ */
+
+#ifndef AUDIOES_MUXER_H_
+#define AUDIOES_MUXER_H_
+
+#include "muxer.h"
+
+muxer_t* audioes_muxer_create (const muxer_config_t* m_cfg);
+
+#endif
return (profile_t *)pro;
}
+
+/*
+ * Audioes Muxer
+ */
+typedef struct profile_audioes {
+ profile_t;
+} profile_audioes_t;
+
+const idclass_t profile_audioes_class =
+{
+ .ic_super = &profile_class,
+ .ic_class = "profile-audioes",
+ .ic_caption = N_("Audioes"),
+ .ic_properties = (const property_t[]){
+ { }
+ }
+};
+
+
+static int
+profile_audioes_reopen(profile_chain_t *prch,
+ muxer_config_t *m_cfg, int flags)
+{
+ muxer_config_t c;
+
+ if (m_cfg)
+ c = *m_cfg; /* do not alter the original parameter */
+ else
+ memset(&c, 0, sizeof(c));
+ c.m_type = MC_AUDIOES;
+
+ assert(!prch->prch_muxer);
+ prch->prch_muxer = muxer_create(&c);
+ return 0;
+}
+
+static int
+profile_audioes_open(profile_chain_t *prch,
+ muxer_config_t *m_cfg, int flags, size_t qsize)
+{
+ int r;
+
+ prch->prch_flags = SUBSCRIPTION_PACKET;
+ prch->prch_sq.sq_maxsize = qsize;
+
+ r = profile_htsp_work(prch, &prch->prch_sq.sq_st, 0, 0);
+ if (r) {
+ profile_chain_close(prch);
+ return r;
+ }
+
+ profile_audioes_reopen(prch, m_cfg, flags);
+ return 0;
+}
+
+static muxer_container_type_t
+profile_audioes_get_mc(profile_t *_pro)
+{
+ return MC_AUDIOES;
+}
+
+static profile_t *
+profile_audioes_builder(void)
+{
+ profile_audioes_t *pro = calloc(1, sizeof(*pro));
+ pro->pro_sflags = SUBSCRIPTION_PACKET;
+ pro->pro_reopen = profile_audioes_reopen;
+ pro->pro_open = profile_audioes_open;
+ pro->pro_get_mc = profile_audioes_get_mc;
+ return (profile_t *)pro;
+}
+
+
+
+
+
+
#if ENABLE_LIBAV
/*
{ N_("WEBM/built-in"), MC_WEBM, },
{ N_("MPEG-TS/av-lib"), MC_MPEGTS },
{ N_("MPEG-PS (DVD)/av-lib"), MC_MPEGPS },
+ { N_("Audioes"), MC_AUDIOES },
{ N_("Matroska (mkv)/av-lib"), MC_AVMATROSKA },
{ N_("WEBM/av-lib"), MC_AVWEBM },
};
case MC_WEBM:
case MC_MPEGTS:
case MC_MPEGPS:
+ case MC_AUDIOES:
case MC_AVMATROSKA:
return 1;
default:
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);
+ profile_register(&profile_audioes_class, profile_audioes_builder);
#if ENABLE_LIBAV
profile_register(&profile_libav_mpegts_class, profile_libav_mpegts_builder);
profile_register(&profile_libav_matroska_class, profile_libav_matroska_builder);
htsmsg_destroy(conf);
}
+ name = "audioes";
+ pro = profile_find_by_name2(name, NULL, 1);
+ if (pro == NULL || strcmp(profile_get_name(pro), name)) {
+ htsmsg_t *conf;
+
+ conf = htsmsg_create_map();
+ htsmsg_add_str (conf, "class", "profile-audioes");
+ htsmsg_add_bool(conf, "enabled", 1);
+ htsmsg_add_str (conf, "name", name);
+ htsmsg_add_str (conf, "comment", _("Audio-only MPEG elementary stream"));
+ htsmsg_add_s32 (conf, "priority", PROFILE_SPRIO_NORMAL);
+ (void)profile_create(NULL, conf, 1);
+ htsmsg_destroy(conf);
+ }
+
#if ENABLE_LIBAV
name = "webtv-vp8-vorbis-webm";
[LS_HEVC] = { "hevc", N_("HEVC - H.265") },
[LS_MUXER] = { "muxer", N_("Muxer") },
[LS_PASS] = { "pass", N_("Pass-thru muxer") },
+ [LS_AUDIOES] = { "audioes", N_("Audioes muxer") },
[LS_MKV] = { "mkv", N_("Matroska muxer") },
[LS_SERVICE] = { "service", N_("Service") },
[LS_CHANNEL] = { "channel", N_("Channel") },
LS_HEVC,
LS_MUXER,
LS_PASS,
+ LS_AUDIOES,
LS_MKV,
LS_SERVICE,
LS_CHANNEL,