Commit c599964d authored by Rémi Denis-Courmont's avatar Rémi Denis-Courmont

cycle: initial support for splitting stream output in time (refs #561)

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.
parent e9ef30f8
......@@ -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
......
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 \
......
/*****************************************************************************
* 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()
......@@ -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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment