]> git.sesse.net Git - vlc/commitdiff
cycle: initial support for splitting stream output in time (refs #561)
authorRémi Denis-Courmont <remi@remlab.net>
Fri, 20 Feb 2015 22:01:23 +0000 (00:01 +0200)
committerRémi Denis-Courmont <remi@remlab.net>
Fri, 20 Feb 2015 22:09:45 +0000 (00:09 +0200)
Example:
 #cycle{duration=20m,
        dst=std{mux=ts,access=file,dst=sport.ts},   duration=5m},
        dst=std{mux=ts,access=file,dst=weather.ts}, duration=5m}

Skips 20 minutes, then records 5 minutes to sport.ts, then 5 more
minutes to weather.ts and restarts.

"duration" specifies the duration of the previous phase
"offset"   specifies the offset at which the previous phase ends
           and the next phase begins
           (mutually exclusive with duration)
"dst"      specifies the stream output chain for the phase
           (if missing, the phase is skipped/discarded)

Durations and offsets are so far expressed as an integer, optionally
followed by a unit: w=week, d=day, h=hour, m=minute, s=second. Second
is the default.

Currently only the decoding time stamp can be used as a reference, but
adding local or UTC clocks should be relatively easy.

ES synchronization and reference frames management is left for
further study.

modules/MODULES_LIST
modules/stream_out/Makefile.am
modules/stream_out/cycle.c [new file with mode: 0644]
po/POTFILES.in

index b048ba13db660c9e6ef324e3f077a279345ea46e..68bf31f2bfcb055389a348e30ff30176ec6614ac 100644 (file)
@@ -345,6 +345,7 @@ $Id$
  * stream_out_bridge: "exchange" streams between sout instances. To be used with VLM
  * stream_out_chromaprint: Audio fingerprinter
  * stream_out_chromecast: Chromecast streaming output module
+ * stream_out_cycle: cyclic stream output chain
  * stream_out_delay: introduce delay in an ES when streaming
  * stream_out_description: helper module for RTSP vod
  * stream_out_display: displays a stream output chain
index 906037cf2025fadcf2274f76305ea4f96d61d823..36fdf15d0fb71cd1fcbc78fa0b486c854fb591df 100644 (file)
@@ -1,6 +1,7 @@
 soutdir = $(pluginsdir)/stream_out
 
 libstream_out_dummy_plugin_la_SOURCES = stream_out/dummy.c
+libstream_out_cycle_plugin_la_SOURCES = stream_out/cycle.c
 libstream_out_delay_plugin_la_SOURCES = stream_out/delay.c
 libstream_out_stats_plugin_la_SOURCES = stream_out/stats.c
 libstream_out_description_plugin_la_SOURCES = stream_out/description.c
@@ -26,6 +27,7 @@ libstream_out_transcode_plugin_la_LIBADD = $(LIBM)
 
 sout_LTLIBRARIES = \
        libstream_out_dummy_plugin.la \
+       libstream_out_cycle_plugin.la \
        libstream_out_delay_plugin.la \
        libstream_out_stats_plugin.la \
        libstream_out_description_plugin.la \
diff --git a/modules/stream_out/cycle.c b/modules/stream_out/cycle.c
new file mode 100644 (file)
index 0000000..fb501ae
--- /dev/null
@@ -0,0 +1,329 @@
+/*****************************************************************************
+ * cycle.c: cycle stream output module
+ *****************************************************************************
+ * Copyright (C) 2015 Rémi Denis-Courmont
+ *
+ * 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Rémi Denis-Courmont reserves the right to redistribute this file under
+ * the terms of the GNU Lesser General Public License as published by the
+ * the Free Software Foundation; either version 2.1 or the License, or
+ * (at his 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, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_block.h>
+#include <vlc_sout.h>
+
+typedef struct sout_cycle sout_cycle_t;
+
+struct sout_cycle
+{
+    sout_cycle_t *next;
+    mtime_t offset;
+    char chain[1];
+};
+
+struct sout_stream_id_sys_t
+{
+    sout_stream_id_sys_t *prev;
+    sout_stream_id_sys_t *next;
+    es_format_t fmt;
+    void *id;
+};
+
+struct sout_stream_sys_t
+{
+    sout_stream_t *stream; /*< Current output stream */
+    sout_stream_id_sys_t *first; /*< First elementary stream */
+    sout_stream_id_sys_t *last; /*< Last elementary stream */
+
+    sout_cycle_t *start;
+    sout_cycle_t *next;
+    mtime_t (*clock)(const block_t *);
+    mtime_t period; /*< Total cycle duration */
+};
+
+static mtime_t get_dts(const block_t *block)
+{
+    return block->i_dts;
+}
+
+static sout_stream_id_sys_t *Add(sout_stream_t *stream, es_format_t *fmt)
+{
+    sout_stream_sys_t *sys = stream->p_sys;
+    sout_stream_id_sys_t *id = malloc(sizeof (*id));
+    if (unlikely(id == NULL))
+        return NULL;
+
+    id->next = NULL;
+
+    if (es_format_Copy(&id->fmt, fmt))
+    {
+        free(id);
+        return NULL;
+    }
+
+    if (sys->stream != NULL)
+        id->id = sout_StreamIdAdd(sys->stream, &id->fmt);
+
+    id->prev = sys->last;
+    sys->last = id;
+    if (id->prev != NULL)
+        id->prev->next = id;
+    else
+        sys->first = id;
+    return id;
+}
+
+static int Del(sout_stream_t *stream, sout_stream_id_sys_t *id)
+{
+    sout_stream_sys_t *sys = stream->p_sys;
+
+    if (id->prev != NULL)
+        id->prev->next = id->next;
+    else
+        sys->first = id->next;
+
+    if (id->next != NULL)
+        id->next->prev = id->prev;
+    else
+        sys->last = id->prev;
+
+    if (sys->stream != NULL)
+        sout_StreamIdDel(sys->stream, id->id);
+
+    es_format_Clean(&id->fmt);
+    free(id);
+    return VLC_SUCCESS;
+}
+
+static int AddStream(sout_stream_t *stream, char *chain)
+{
+    sout_stream_sys_t *sys = stream->p_sys;
+
+    msg_Dbg(stream, "starting new phase \"%s\"", chain);
+    /* TODO format */
+    sys->stream = sout_StreamChainNew(stream->p_sout, chain,
+                                      stream->p_next, NULL);
+    if (sys->stream == NULL)
+        return -1;
+
+    for (sout_stream_id_sys_t *id = sys->first; id != NULL; id = id->next)
+        id->id = sout_StreamIdAdd(sys->stream, &id->fmt);
+
+    return 0;
+}
+
+static void DelStream(sout_stream_t *stream)
+{
+    sout_stream_sys_t *sys = stream->p_sys;
+
+    if (sys->stream == NULL)
+        return;
+
+    for (sout_stream_id_sys_t *id = sys->first; id != NULL; id = id->next)
+        if (id->id != NULL)
+            sout_StreamIdDel(sys->stream, id->id);
+
+    sout_StreamChainDelete(sys->stream, NULL);
+    sys->stream = NULL;
+}
+
+static int Send(sout_stream_t *stream, sout_stream_id_sys_t *id,
+                block_t *block)
+{
+    sout_stream_sys_t *sys = stream->p_sys;
+
+    for (block_t *next = block->p_next; block != NULL; block = next)
+    {
+        block->p_next = NULL;
+
+        /* FIXME: deal with key frames properly */
+        while (sys->clock(block) >= sys->next->offset)
+        {
+            DelStream(stream);
+            AddStream(stream, sys->next->chain);
+
+            sys->next->offset += sys->period;
+            sys->next = sys->next->next;
+            if (sys->next == NULL)
+                sys->next = sys->start;
+        }
+
+        if (sys->stream != NULL)
+            sout_StreamIdSend(sys->stream, id->id, block);
+        else
+            block_Release(block);
+    }
+    return VLC_SUCCESS;
+}
+
+static int AppendPhase(sout_cycle_t ***restrict pp,
+                       mtime_t offset, const char *chain)
+{
+
+    size_t len = strlen(chain);
+    sout_cycle_t *cycle = malloc(sizeof (*cycle) + len);
+    if (unlikely(cycle == NULL))
+        return -1;
+
+    cycle->next = NULL;
+    cycle->offset = offset;
+    memcpy(cycle->chain, chain, len + 1);
+
+    **pp = cycle;
+    *pp = &cycle->next;
+    return 0;
+}
+
+static mtime_t ParseTime(const char *str)
+{
+    char *end;
+    unsigned long long u = strtoull(str, &end, 0);
+
+    switch (*end)
+    {
+        case 'w':
+            if (u > 15250284U)
+                return -1;
+            return CLOCK_FREQ * 604800LLU * u;
+        case 'd':
+            if (u > 106751991U)
+                return -1;
+            return CLOCK_FREQ * 86400LLU * u;
+        case 'h':
+            if (u > 2562047788U)
+                return -1;
+            return CLOCK_FREQ * 3600LLU * u;
+        case 'm':
+            if (u > 153722867280U)
+                return -1;
+            return CLOCK_FREQ * 60LLU * u;
+        case 's':
+        case 0:
+            if (u > 9223372036854U)
+                return -1;
+            return CLOCK_FREQ * u;
+    }
+    return -1;
+}
+
+static int Open(vlc_object_t *obj)
+{
+    sout_stream_t *stream = (sout_stream_t *)obj;
+    sout_stream_sys_t *sys = malloc(sizeof (*sys));
+    if (unlikely(sys == NULL))
+        return VLC_ENOMEM;
+
+    sys->stream = NULL;
+    sys->first = NULL;
+    sys->last = NULL;
+    sys->start = NULL;
+    sys->clock = get_dts;
+
+    mtime_t offset = 0;
+    sout_cycle_t **pp = &sys->start;
+    const char *chain = "";
+
+    for (const config_chain_t *cfg = stream->p_cfg;
+         cfg != NULL;
+         cfg = cfg->p_next)
+    {
+        if (!strcmp(cfg->psz_name, "dst"))
+        {
+            chain = cfg->psz_value;
+        }
+        else if (!strcmp(cfg->psz_name, "duration"))
+        {
+            mtime_t t = ParseTime(cfg->psz_value);
+
+            if (t > 0)
+            {
+                AppendPhase(&pp, offset, chain);
+                offset += t;
+            }
+
+            chain = "";
+        }
+        else if (!strcmp(cfg->psz_name, "offset"))
+        {
+            mtime_t t = ParseTime(cfg->psz_value);
+
+            if (t > offset)
+            {
+                AppendPhase(&pp, offset, chain);
+                offset = t;
+            }
+
+            chain = "";
+        }
+        else
+        {
+            msg_Err(stream, "unknown option \"%s\"", cfg->psz_name);
+        }
+    }
+
+    if (sys->start == NULL || offset <= 0)
+    {
+        free(sys);
+        msg_Err(stream, "unknown or invalid cycle specification");
+        return VLC_EGENERIC;
+    }
+
+    sys->next = sys->start;
+    sys->period = offset;
+
+    stream->pf_add = Add;
+    stream->pf_del = Del;
+    stream->pf_send = Send;
+    stream->p_sys = sys;
+    return VLC_SUCCESS;
+}
+
+static void Close(vlc_object_t *obj)
+{
+    sout_stream_t *stream = (sout_stream_t *)obj;
+    sout_stream_sys_t *sys = stream->p_sys;
+
+    assert(sys->first == NULL && sys->last == NULL);
+
+    if (sys->stream != NULL)
+        sout_StreamChainDelete(sys->stream, NULL);
+
+    for (sout_cycle_t *cycle = sys->start, *next; cycle != NULL; cycle = next)
+    {
+        next = cycle->next;
+        free(cycle);
+    }
+
+    free(sys);
+}
+
+vlc_module_begin()
+    set_description(N_("Cyclic stream output"))
+    set_capability("sout stream", 0)
+    set_category(CAT_SOUT)
+    set_subcategory(SUBCAT_SOUT_STREAM)
+    set_callbacks(Open, Close)
+vlc_module_end()
index f1ab9dcb16190e3d6cf5f58950f29558d88af71a..b7d5c57208dac5b0cfd577326a3c200db259e825 100644 (file)
@@ -1027,6 +1027,7 @@ modules/stream_filter/record.c
 modules/stream_filter/smooth/smooth.c
 modules/stream_out/autodel.c
 modules/stream_out/bridge.c
+modules/stream_out/cycle.c
 modules/stream_out/delay.c
 modules/stream_out/description.c
 modules/stream_out/display.c