Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • videolan/vlc
  • chouquette/vlc
  • bakiewicz.marek122/vlc
  • devnexen/vlc
  • rohanrajpal/vlc
  • blurrrb/vlc
  • gsoc/gsoc2019/darkapex/vlc
  • b1ue/vlc
  • fkuehne/vlc
  • magsoft/vlc
  • chub/vlc
  • cramiro9/vlc
  • robUx4/vlc
  • rom1v/vlc
  • akshayaky/vlc
  • tmk907/vlc
  • akymaster/vlc
  • govind.sharma/vlc
  • psilokos/vlc
  • xjbeta/vlc
  • jahan/vlc
  • 1480c1/vlc
  • amanchande/vlc
  • aaqib/vlc
  • rist/vlc
  • apol/vlc
  • mindfreeze/vlc
  • alexandre-janniaux/vlc
  • sandsmark/vlc
  • jagannatharjun/vlc
  • gsoc/gsoc2020/matiaslgonzalez/vlc
  • gsoc/gsoc2020/jagannatharjun/vlc
  • mstorsjo/vlc
  • gsoc/gsoc2020/vedenta/vlc
  • gsoc/gsoc2020/arnav-ishaan/vlc
  • gsoc/gsoc2020/andreduong/vlc
  • fuzun/vlc
  • gsoc/gsoc2020/vatsin/vlc
  • gsoc/gsoc2020/sagid/vlc
  • yaron/vlc
  • Phoenix/vlc
  • Garf/vlc
  • ePiratWorkarounds/vlc
  • tguillem/vlc
  • jnqnfe/vlc
  • mdc/vlc
  • Vedaa/vlc
  • rasa/vlc
  • quink/vlc
  • yealo/vlc
  • aleksey_ak/vlc
  • ePirat/vlc
  • ilya.yanok/vlc
  • asenat/vlc
  • m/vlc
  • bunjee/vlc
  • BLumia/vlc
  • sagudev/vlc
  • hamedmonji30/vlc
  • nullgemm/vlc
  • DivyamAhuja/vlc
  • thesamesam/vlc
  • dag7/vlc
  • snehil101/vlc
  • haasn/vlc
  • jbk/vlc
  • ValZapod/vlc
  • mfkl/vlc
  • WangChuan/vlc
  • core1024/vlc
  • GhostVaibhav/vlc
  • dfuhrmann/vlc
  • davide.prade/vlc
  • tmatth/vlc
  • Courmisch/vlc
  • zouya/vlc
  • hpi/vlc
  • EwoutH/vlc
  • aleung27/vlc
  • hengwu0/vlc
  • saladin/vlc
  • ashuio/vlc
  • richselwood/vlc
  • verma16Ayush/vlc
  • chemicalflash/vlc
  • PoignardAzur/vlc
  • huangjieNT/vlc
  • Blake-Haydon/vlc
  • AnuthaDev/vlc
  • gsoc/gsoc2021/mpd/vlc
  • nicolas_lequec/vlc
  • sambassaly/vlc
  • thresh/vlc
  • bonniegong/vlc
  • myaashish/vlc
  • stavros.vagionitis/vlc
  • ileoo/vlc
  • louis-santucci/vlc
  • cchristiansen/vlc
  • sabyasachi07/vlc
  • AbduAmeen/vlc
  • ashishb0410/vlc
  • urbanhusky/vlc
  • davidepietrasanta/vlc
  • riksleutelstad/vlc
  • jeremyVignelles/vlc
  • komh/vlc
  • iamjithinjohn/vlc
  • JohannesKauffmann/vlc2
  • kunglao/vlc
  • natzberg/vlc
  • jill/vlc
  • cwendling/vlc
  • adufou/vlc
  • ErwanAirone/vlc
  • HasinduDilshan10/vlc
  • vagrantc/vlc
  • rafiv/macos-bigsur-icon
  • Aymeriic/vlc
  • saranshg20/vlc
  • metzlove24/vlc
  • linkfanel/vlc
  • Ds886/vlc
  • metehan-arslan/vlc
  • Skantes/vlc
  • kgsandundananjaya96/vlc
  • mitchcapper/vlc
  • advaitgupta/vlc
  • StefanBruens/vlc
  • ratajs/vlc
  • T.M.F.B.3761/vlc
  • m222059/vlc
  • casemerrick/vlc
  • joshuaword2alt/vlc
  • sjwaddy/vlc
  • dima/vlc
  • Ybalrid/vlc
  • umxprime/vlc
  • eschmidt/vlc
  • vannieuwenhuysenmichelle/vlc
  • badcf00d/vlc
  • wesinator/vlc
  • louis/vlc
  • xqq/vlc
  • EmperorYP7/vlc
  • NicoLiam/vlc
  • loveleen/vlc
  • rofferom/vlc
  • rbultje/vlc
  • TheUnamed/vlc
  • pratiksharma341/vlc
  • Saurab17/vlc
  • purist.coder/vlc
  • Shuicheng/vlc
  • mdrrubel292/vlc
  • silverbleu00/vlc
  • metif12/vlc
  • asher-m/vlc
  • jeffk/vlc
  • Brandonbr1/vlc
  • beautyyuyanli/vlc
  • rego21/vlc
  • muyangren907/vlc
  • collectionbylawrencejason/vlc
  • evelez/vlc
  • GSMgeeth/vlc
  • Oneric/vlc
  • TJ5/vlc
  • XuanTung95/vlc
  • darrenjenny21/vlc
  • Trenly/vlc
  • RockyTDR/vlc
  • mjakubowski/vlc
  • caprica/vlc
  • ForteFrankie/vlc
  • seannamiller19/vlc
  • junlon2006/vlc
  • kiwiren6666/vlc
  • iuseiphonexs/vlc
  • fenngtun/vlc
  • Rajdutt999/vlc
  • typx/vlc
  • leon.vitanos/vlc
  • robertogarci0938/vlc
  • gsoc/gsoc2022/luc65r/vlc-mpd
  • skeller/vlc
  • MCJack123/vlc
  • luc65r/vlc-mpd
  • popov895/vlc
  • claucambra/vlc
  • brad/vlc
  • matthewmurua88/vlc
  • Tomas8874/vlc
  • philenotfound/vlc
  • makita-do3/vlc
  • LZXCorp/vlc
  • mar0x/vlc
  • senojetkennedy0102/vlc
  • shaneb243/vlc
  • ahmadbader/vlc
  • rajduttcse26/vlc-audio-filters
  • Juniorzito8415/vlc
  • achernyakov/vlc
  • lucasjetgroup/vlc
  • pupdoggy666/vlc
  • gmde9363/vlc
  • alexnwayne/vlc
  • bahareebrahimi781/vlc
  • hamad633666/vlc
  • umghof3112/vlc
  • joe0199771874/vlc
  • Octocats66666666/vlc
  • jjm_223/vlc
  • btech10110.19/vlc
  • sunnykfc028/vlc-audio-filters
  • loic/vlc
  • nguyenminhducmx1/vlc
  • JanekKrueger/vlc
  • bstubbington2/vlc
  • rcombs/vlc
  • Ordissimo/vlc
  • king7532/vlc
  • noobsauce101/vlc
  • schong0525/vlc
  • myQwil/vlc
  • apisbg91/vlc
  • geeboy0101017/vlc
  • kim.faughey/vlc
  • nurupo/vlc
  • yyusea/vlc
  • 0711235879.khco/vlc
  • ialo/vlc
  • iloveyeye2/vlc
  • gdtdftdqtd/vlc
  • leandroconsiglio/vlc
  • AndyHTML2012/vlc
  • ncz/vlc
  • lucenticus/vlc
  • knr1931/vlc
  • kjoonlee/vlc
  • chandrakant100/vlc-qt
  • johge42/vlc
  • polter/vlc
  • hexchain/vlc
  • Tushwrld/vlc
  • mztea928/vlc
  • jbelloncastro/vlc
  • alvinhochun/vlc
  • ghostpiratecrow/vlc
  • ujjwaltwitx/vlc
  • alexsonarin06/vlc
  • adrianbon76/vlc
  • altsod/vlc
  • damien.lucas44/vlc
  • dmytrivtaisa/vlc
  • utk202/vlc
  • aaxhrj/vlc
  • thomas.hermes/vlc
  • structurenewworldorder/vlc
  • slomo/vlc
  • wantlamy/vlc
  • musc.o3cminc/vlc
  • thebarshablog/vlc
  • kerrick/vlc
  • kratos142518/vlc
  • leogps/vlc
  • vacantron/vlc
  • luna_koly/vlc
  • Ratio2/vlc
  • anuoshemohammad/vlc
  • apsun/vlc
  • aaa1115910/vlc
  • alimotmoyo/vlc
  • Ambossmann/vlc
  • Sam-LearnsToCode/vlc
  • Chilledheart/vlc
  • Labnann/vlc
  • ktcoooot1/vlc
  • mohit-marathe/vlc
  • johnddx/vlc
  • manstabuk/vlc
  • Omar-ahmed314/vlc
  • vineethkm/vlc
  • 9Enemi86/vlc
  • radoslav.m.panteleev/vlc
  • ashishami2002/vlc
  • Corbax/vlc
  • firnasahmed/vlc
  • pelayarmalam4/vlc
  • c0ff330k/vlc
  • shikhindahikar/vlc
  • l342723951/vlc
  • christianschwandner/vlc
  • douniwan5788/vlc
  • 7damian7/vlc
  • ferdnyc/vlc
  • f.ales1/vlc
  • pandagby/vlc
  • BaaBaa/vlc
  • jewe37/vlc
  • w00drow/vlc
  • russelltg/vlc
  • ironicallygod/vlc
  • soumyaDghosh/vlc
  • linzihao1999/vlc
  • deyayush6/vlc
  • mibi88/vlc
  • newabdallah10/vlc
  • jhorbincolombia/vlc
  • rimvihaqueshupto/vlc
  • andrewkhon98/vlc
  • fab78/vlc
  • lapaz17/vlc
  • amanna13/vlc
  • mdakram28/vlc
  • 07jw1980/vlc
  • sohamgupta/vlc
  • Eson-Jia1/vlc
  • Sumou/vlc
  • vikram-kangotra/vlc
  • chalice191/vlc
  • olivercalder/vlc
  • aaasg4001/vlc
  • zipdox/vlc
  • kwizart/vlc
  • Dragon-S/vlc
  • jdemeule/vlc
  • gabriel_lt/vlc
  • locutusofborg/vlc
  • sammirata/vlc-librist
  • another/vlc
  • Benjamin_Loison/vlc
  • ahmedmoselhi/vlc
  • petergaal/vlc
  • huynhsontung/vlc
  • dariusmihut/vlc
  • tvermaashutosh/vlc
  • buti/vlc
  • Niram7777/vlc
  • rohan-here/vlc
  • balaji-sivasakthi/vlc
  • rlindner81/vlc
  • Kakadus/vlc
  • djain/vlc
  • ABBurmeister/vlc
  • craighuggins/vlc
  • orbea/vlc
  • maxos/vlc
  • aakarshmj/vlc
  • kblaschke/vlc
  • ankitm/vlc
  • advait-0/vlc
  • mohak2003/vlc
  • yselkowitz/vlc
  • AZM999/vlc-azm
  • andrey.turkin/vlc
  • Disha-Baghel/vlc
  • nowrep/vlc
  • Apeng/vlc
  • Choucroute_melba/vlc
  • autra/vlc
  • eclipseo/vlc
  • fhuber/vlc
  • olafhering/vlc
  • sdasda7777/vlc
  • 1div0/vlc
  • skosnits/vlc-extended-playlist-support
  • dnicolson/vlc
  • Timshel/vlc
  • octopols/vlc
  • MangalK/vlc
  • nima64/vlc
  • misawai/vlc
  • Alexander-Wilms/vlc
  • Maxime2/vlc-fork-for-visualizer
  • ww/vlc
  • jeske/vlc
  • sgross-emlix/vlc
  • morenonatural/vlc
  • freakingLovesVLC/vlc
  • borisgolovnev/vlc
  • mpromonet/vlc
  • diogo.simao-marques/vlc
  • masstock/vlc
  • pratikpatel8982/vlc
  • hugok79/vlc
  • longervision/vlc
  • abhiudaysurya/vlc
  • rishabhgarg/vlc
  • tumic/vlc
  • cart/vlc
  • shubham442/vlc
  • Aditya692005/vlc
  • sammirata/vlc4
  • syrykh/vlc
  • Vvorcun/macos-new-icon
  • AyaanshC/vlc
  • nasso/vlc
  • Quark/vlc
  • sebastinas/vlc
  • rhstone/vlc
  • talregev/vlc
  • Managor/vlc
  • abdsaber000/vlc
  • falbrechtskirchinger/vlc
  • b.sullender/vlc
  • hulxv/vlc
  • zyad-ayad/vlc
408 results
Show changes
Commits on Source (15)
......@@ -188,7 +188,10 @@ static int Mux(sout_mux_t *mux)
}
if (data)
{
data->i_flags |= BLOCK_FLAG_HEADER;
sout_AccessOutWrite(mux->p_access, data);
}
sys->header_done = true;
}
......
......@@ -40,7 +40,8 @@ libstream_out_hls_plugin_la_SOURCES = \
stream_out/hls/variant_maps.h stream_out/hls/variant_maps.c \
stream_out/hls/storage.h stream_out/hls/storage.c \
stream_out/hls/segments.h stream_out/hls/segments.c \
stream_out/hls/codecs.h stream_out/hls/codecs.c
stream_out/hls/codecs.h stream_out/hls/codecs.c \
stream_out/hls/subtitles_segmenter.c
libstream_out_hls_plugin_la_LIBADD = libvlc_hxxxhelper.la
sout_LTLIBRARIES = \
......
......@@ -55,6 +55,12 @@ static int FormatMP4A(struct vlc_memstream *ms, const es_format_t *fmt)
return (wrote == -1) ? VLC_ENOMEM : VLC_SUCCESS;
}
static int FormatWebVTT(struct vlc_memstream *ms)
{
const int written = vlc_memstream_puts(ms, "wvtt");
return (written <= 0) ? VLC_ENOMEM : VLC_SUCCESS;
}
int hls_codec_Format(struct vlc_memstream *ms, const es_format_t *fmt)
{
switch (fmt->i_codec)
......@@ -63,6 +69,9 @@ int hls_codec_Format(struct vlc_memstream *ms, const es_format_t *fmt)
return FormatAVC1(ms, fmt);
case VLC_CODEC_MP4A:
return FormatMP4A(ms, fmt);
case VLC_CODEC_TEXT:
case VLC_CODEC_WEBVTT:
return FormatWebVTT(ms);
default:
return VLC_ENOTSUP;
}
......@@ -70,5 +79,6 @@ int hls_codec_Format(struct vlc_memstream *ms, const es_format_t *fmt)
bool hls_codec_IsSupported(const es_format_t *fmt)
{
return fmt->i_codec == VLC_CODEC_H264 || fmt->i_codec == VLC_CODEC_MP4A;
return fmt->i_codec == VLC_CODEC_H264 || fmt->i_codec == VLC_CODEC_MP4A ||
fmt->i_codec == VLC_CODEC_TEXT || fmt->i_codec == VLC_CODEC_WEBVTT;
}
......@@ -47,6 +47,7 @@ typedef struct
block_t *begin;
block_t **end;
vlc_tick_t length;
block_t *last_header;
} hls_block_chain_t;
static inline void hls_block_chain_Reset(hls_block_chain_t *chain)
......@@ -54,6 +55,7 @@ static inline void hls_block_chain_Reset(hls_block_chain_t *chain)
chain->begin = NULL;
chain->end = &chain->begin;
chain->length = 0;
chain->last_header = NULL;
}
/**
......@@ -64,7 +66,7 @@ typedef struct hls_playlist
unsigned int id;
const struct hls_config *config;
size_t *current_memory_cached_ref;
enum hls_playlist_type type;
sout_access_out_t *access;
sout_mux_t *mux;
......@@ -145,8 +147,6 @@ typedef struct
httpd_url_t *http_manifest;
vlc_tick_t first_pcr;
vlc_tick_t last_pcr;
vlc_tick_t last_segment;
size_t current_memory_cached;
} sout_stream_sys_t;
......@@ -301,10 +301,12 @@ static struct hls_storage *GenerateMainManifest(const sout_stream_sys_t *sys)
static const char *const TRACK_TYPES[] = {
[VIDEO_ES] = "VIDEO",
[AUDIO_ES] = "AUDIO",
[SPU_ES] = "SUBTITLES",
};
static const char *const GROUP_IDS[] = {
[VIDEO_ES] = "video",
[AUDIO_ES] = "audio",
[SPU_ES] = "subtitles",
};
const hls_playlist_t *playlist;
......@@ -312,7 +314,8 @@ static struct hls_storage *GenerateMainManifest(const sout_stream_sys_t *sys)
{
const hls_track_t *track = MediaGetTrack(playlist);
const es_format_t *fmt = &track->input->fmt;
assert(fmt->i_cat == VIDEO_ES || fmt->i_cat == AUDIO_ES);
assert(fmt->i_cat == VIDEO_ES || fmt->i_cat == AUDIO_ES ||
fmt->i_cat == SPU_ES);
MANIFEST_START_TAG("#EXT-X-MEDIA")
const char *track_type = TRACK_TYPES[fmt->i_cat];
......@@ -360,6 +363,7 @@ static struct hls_storage *GenerateMainManifest(const sout_stream_sys_t *sys)
MANIFEST_ADD_ATTRIBUTE("VIDEO=\"%s\"", GROUP_IDS[VIDEO_ES]);
MANIFEST_ADD_ATTRIBUTE("AUDIO=\"%s\"", GROUP_IDS[AUDIO_ES]);
MANIFEST_ADD_ATTRIBUTE("SUBTITLES=\"%s\"", GROUP_IDS[SPU_ES]);
MANIFEST_END_TAG
if (vlc_memstream_printf(&out, "%s\n", playlist->url) < 0)
......@@ -462,35 +466,161 @@ static int UpdatePlaylistManifest(hls_playlist_t *playlist)
return VLC_SUCCESS;
}
static hls_block_chain_t ExtractCommonSegment(hls_block_chain_t *muxed_output,
vlc_tick_t max_segment_length)
{
hls_block_chain_t segment = {.begin = muxed_output->begin};
block_t *prev = NULL;
for (block_t *it = muxed_output->begin; it != NULL; it = it->p_next)
{
if (segment.length + it->i_length > max_segment_length)
{
muxed_output->begin = it;
if (prev != NULL)
prev->p_next = NULL;
return segment;
}
segment.length += it->i_length;
muxed_output->length -= it->i_length;
prev = it;
}
hls_block_chain_Reset(muxed_output);
return segment;
}
static hls_block_chain_t ExtractSubtitleSegment(hls_block_chain_t *muxed_output,
vlc_tick_t segment_length)
{
hls_block_chain_t segment = {.begin = muxed_output->begin,
.length = segment_length};
for (block_t *it = muxed_output->begin; it != NULL; it = it->p_next)
{
/* Subtitle segments are segmented at mux level by the
* hls_sub_segmenter. They have varying length so we use the header flag
* to extract them properly. */
if (it->p_next != NULL && it->p_next->i_flags & BLOCK_FLAG_HEADER)
{
muxed_output->begin = it->p_next;
muxed_output->last_header = it->p_next;
it->p_next = NULL;
return segment;
}
muxed_output->length -= it->i_length;
}
hls_block_chain_Reset(muxed_output);
return segment;
}
static hls_block_chain_t ExtractSegment(hls_playlist_t *playlist)
{
const vlc_tick_t seglen = playlist->config->segment_length;
if (playlist->type == HLS_PLAYLIST_TYPE_WEBVTT)
return ExtractSubtitleSegment(&playlist->muxed_output, seglen);
return ExtractCommonSegment(&playlist->muxed_output, seglen);
}
static int ExtractAndAddSegment(hls_playlist_t *playlist,
sout_stream_sys_t *sys)
{
hls_block_chain_t segment = ExtractSegment(playlist);
if (hls_config_IsMemStorageEnabled(&sys->config) &&
hls_segment_queue_IsAtMaxCapacity(&playlist->segments))
{
const hls_segment_t *to_be_removed =
hls_segment_GetFirst(&playlist->segments);
sys->current_memory_cached -=
hls_storage_GetSize(to_be_removed->storage);
}
const int status = hls_segment_queue_NewSegment(
&playlist->segments, segment.begin, segment.length);
if (unlikely(status != VLC_SUCCESS))
{
vlc_error(playlist->logger,
"Segment '%u' creation failed",
playlist->segments.total_segments + 1);
return status;
}
vlc_debug(playlist->logger,
"Segment '%u' created",
playlist->segments.total_segments);
return UpdatePlaylistManifest(playlist);
}
static bool IsSegmentReady(enum hls_playlist_type type,
hls_block_chain_t *buffer,
vlc_tick_t seglen)
{
/* The subtitle header outputs one header per segment. Let's wait until we
* received the next header before considering the current segment
* finished. */
if( type == HLS_PLAYLIST_TYPE_WEBVTT)
return buffer->begin != buffer->last_header;
/* Only consider full segments as ready for now. */
return buffer->length >= seglen;
}
static ssize_t AccessOutWrite(sout_access_out_t *access, block_t *block)
{
hls_playlist_t *playlist = access->p_sys;
sout_stream_sys_t *sys = access->p_sys;
size_t size = 0;
block_ChainProperties(block, NULL, &size, NULL);
vlc_tick_t length;
block_ChainProperties(block, NULL, &size, &length);
if (hls_config_IsMemStorageEnabled(playlist->config))
if (hls_config_IsMemStorageEnabled(&sys->config))
{
*playlist->current_memory_cached_ref += size;
if (*playlist->current_memory_cached_ref >=
playlist->config->max_memory)
sys->current_memory_cached += size;
if (sys->current_memory_cached >= sys->config.max_memory)
{
vlc_error(playlist->logger,
"Maximum memory capacity (%zuKb) for segment storage was "
"reached. The HLS server will stop creating segments. "
"Please refer to the max-memory option for more info.",
BYTES_TO_KB(playlist->config->max_memory));
msg_Err(access,
"Maximum memory capacity (%zuKb) for segment storage was "
"reached. The HLS server will stop creating segments. "
"Please refer to the max-memory option for more info.",
BYTES_TO_KB(sys->config.max_memory));
block_ChainRelease(block);
return -1;
}
}
block_ChainLastAppend(&playlist->muxed_output.end, block);
bool segments_ready = true;
hls_playlist_t *it;
hls_playlists_foreach(it)
{
/* Append the muxed output to the playlist tied to this access call. */
if (it->access == access)
{
block_ChainLastAppend(&it->muxed_output.end, block);
it->muxed_output.length += length;
if (block->i_flags & BLOCK_FLAG_HEADER)
it->muxed_output.last_header = block;
}
if (!IsSegmentReady(
it->type, &it->muxed_output, sys->config.segment_length))
segments_ready = false;
}
if (segments_ready)
{
hls_playlists_foreach (it)
{
if (ExtractAndAddSegment(it, sys) != VLC_SUCCESS)
return -1;
}
}
return size;
}
static sout_access_out_t *CreateAccessOut(sout_stream_t *stream,
hls_playlist_t *sys)
static sout_access_out_t *CreateAccessOut(sout_stream_t *stream)
{
sout_access_out_t *access = vlc_object_create(stream, sizeof(*access));
if (unlikely(access == NULL))
......@@ -505,7 +635,7 @@ static sout_access_out_t *CreateAccessOut(sout_stream_t *stream,
access->p_cfg = NULL;
access->p_module = NULL;
access->p_sys = sys;
access->p_sys = stream->p_sys;
access->psz_path = NULL;
access->pf_control = NULL;
......@@ -527,7 +657,22 @@ static inline char *FormatPlaylistManifestURL(const hls_playlist_t *playlist)
return url;
}
static hls_playlist_t *CreatePlaylist(sout_stream_t *stream)
static sout_mux_t *CreatePlaylistMuxer(sout_access_out_t *access,
enum hls_playlist_type type,
const struct hls_config *config)
{
switch(type)
{
case HLS_PLAYLIST_TYPE_TS:
return sout_MuxNew(access, "ts");
case HLS_PLAYLIST_TYPE_WEBVTT:
return CreateSubtitleSegmenter(access, config);
}
return NULL;
}
static hls_playlist_t *CreatePlaylist(sout_stream_t *stream,
enum hls_playlist_type type)
{
sout_stream_sys_t *sys = stream->p_sys;
......@@ -535,18 +680,18 @@ static hls_playlist_t *CreatePlaylist(sout_stream_t *stream)
if (unlikely(playlist == NULL))
return NULL;
playlist->access = CreateAccessOut(stream, playlist);
playlist->access = CreateAccessOut(stream);
if (unlikely(playlist->access == NULL))
goto access_err;
playlist->mux = sout_MuxNew(playlist->access, "ts");
playlist->mux = CreatePlaylistMuxer(playlist->access, type, &sys->config);
if (unlikely(playlist->mux == NULL))
goto mux_err;
playlist->id = sys->playlist_created_count;
playlist->type = type;
playlist->config = &sys->config;
playlist->ended = false;
playlist->current_memory_cached_ref = &sys->current_memory_cached;
playlist->url = FormatPlaylistManifestURL(playlist);
if (unlikely(playlist->url == NULL))
......@@ -560,6 +705,7 @@ static hls_playlist_t *CreatePlaylist(sout_stream_t *stream)
struct hls_segment_queue_config config = {
.playlist_id = playlist->id,
.playlist_type = type,
.httpd_ref = sys->http_host,
.httpd_callback = HTTPCallback,
};
......@@ -626,9 +772,11 @@ static void DeletePlaylist(hls_playlist_t *playlist)
free(playlist);
}
static hls_playlist_t *AddPlaylist(sout_stream_t *stream, struct vlc_list *head)
static hls_playlist_t *AddPlaylist(sout_stream_t *stream,
enum hls_playlist_type type,
struct vlc_list *head)
{
hls_playlist_t *variant = CreatePlaylist(stream);
hls_playlist_t *variant = CreatePlaylist(stream, type);
if (variant == NULL)
return NULL;
......@@ -654,11 +802,15 @@ Add(sout_stream_t *stream, const es_format_t *fmt, const char *es_id)
if (map != NULL)
{
if (map->playlist_ref == NULL)
map->playlist_ref = AddPlaylist(stream, &sys->variant_playlists);
map->playlist_ref = AddPlaylist(stream, HLS_PLAYLIST_TYPE_TS, &sys->variant_playlists);
playlist = map->playlist_ref;
}
else if (fmt->i_cat == SPU_ES)
playlist = AddPlaylist(
stream, HLS_PLAYLIST_TYPE_WEBVTT, &sys->media_playlists);
else
playlist = AddPlaylist(stream, &sys->media_playlists);
playlist =
AddPlaylist(stream, HLS_PLAYLIST_TYPE_TS, &sys->media_playlists);
if (playlist == NULL)
return NULL;
......@@ -709,61 +861,6 @@ error:
return NULL;
}
static hls_block_chain_t ExtractSegment(hls_playlist_t *playlist,
vlc_tick_t max_segment_length)
{
hls_block_chain_t segment = {.begin = playlist->muxed_output.begin};
block_t *prev = NULL;
for (block_t *it = playlist->muxed_output.begin; it != NULL;
it = it->p_next)
{
if (segment.length + it->i_length > max_segment_length)
{
playlist->muxed_output.begin = it;
if (prev != NULL)
prev->p_next = NULL;
return segment;
}
segment.length += it->i_length;
prev = it;
}
hls_block_chain_Reset(&playlist->muxed_output);
return segment;
}
static void ExtractAndAddSegment(hls_playlist_t *playlist,
vlc_tick_t last_segment_time)
{
hls_block_chain_t segment = ExtractSegment(playlist, last_segment_time);
if (hls_config_IsMemStorageEnabled(playlist->config) &&
hls_segment_queue_IsAtMaxCapacity(&playlist->segments))
{
const hls_segment_t *to_be_removed =
hls_segment_GetFirst(&playlist->segments);
*playlist->current_memory_cached_ref -=
hls_storage_GetSize(to_be_removed->storage);
}
const int status = hls_segment_queue_NewSegment(
&playlist->segments, segment.begin, segment.length);
if (unlikely(status != VLC_SUCCESS))
{
vlc_error(playlist->logger,
"Segment '%u' creation failed",
playlist->segments.total_segments + 1);
return;
}
vlc_debug(playlist->logger,
"Segment '%u' created",
playlist->segments.total_segments);
UpdatePlaylistManifest(playlist);
}
static void Del(sout_stream_t *stream, void *id)
{
sout_stream_sys_t *sys = stream->p_sys;
......@@ -780,7 +877,7 @@ static void Del(sout_stream_t *stream, void *id)
map->playlist_ref = NULL;
track->playlist_ref->ended = true;
ExtractAndAddSegment(track->playlist_ref, sys->config.segment_length);
ExtractAndAddSegment(track->playlist_ref, sys);
UpdatePlaylistManifest(track->playlist_ref);
DeletePlaylist(track->playlist_ref);
......@@ -796,17 +893,11 @@ static int Send(sout_stream_t *stream, void *id, vlc_frame_t *frame)
(void)stream;
}
/**
* PCR events are used to have a reliable stream time status. Segmenting is done
* after a PCR testifying that we are above the segment limit arrives.
*/
/** PCR events are used to have a reliable stream time status. */
static void SetPCR(sout_stream_t *stream, vlc_tick_t pcr)
{
sout_stream_sys_t *sys = stream->p_sys;
const vlc_tick_t last_pcr = sys->last_pcr;
sys->last_pcr = pcr;
if (sys->first_pcr == VLC_TICK_INVALID)
{
sys->first_pcr = pcr;
......@@ -814,23 +905,13 @@ static void SetPCR(sout_stream_t *stream, vlc_tick_t pcr)
}
const vlc_tick_t stream_time = pcr - sys->first_pcr;
const vlc_tick_t current_seglen = stream_time - sys->last_segment;
const vlc_tick_t pcr_gap = pcr - last_pcr;
/* PCR and segment length aren't necessarily aligned. Testing segment length
* with a **next** PCR approximation will avoid piling up data:
*
* |------x#|-----x##|----x###| time
* ^ PCR ^ Segment end ^ Buffer expanding
*
* The segments are then a little shorter than they could be.
*/
if (current_seglen + pcr_gap >= sys->config.segment_length)
const hls_playlist_t *playlist;
vlc_list_foreach_const (playlist, &sys->media_playlists, node)
{
hls_playlist_t *playlist;
hls_playlists_foreach (playlist)
ExtractAndAddSegment(playlist, sys->config.segment_length);
sys->last_segment = stream_time;
if (playlist->type != HLS_PLAYLIST_TYPE_WEBVTT)
continue;
hls_sub_segmenter_SignalStreamUpdate(playlist->mux, stream_time);
}
}
......@@ -974,8 +1055,6 @@ static int Open(vlc_object_t *this)
vlc_list_init(&sys->media_playlists);
sys->first_pcr = VLC_TICK_INVALID;
sys->last_pcr = VLC_TICK_INVALID;
sys->last_segment = 0;
sys->current_memory_cached = 0;
......
......@@ -20,6 +20,12 @@
#ifndef HLS_H
#define HLS_H
enum hls_playlist_type
{
HLS_PLAYLIST_TYPE_TS,
HLS_PLAYLIST_TYPE_WEBVTT,
};
struct hls_config
{
char *base_url;
......@@ -45,4 +51,9 @@ hls_config_IsMemStorageEnabled(const struct hls_config *config)
return config->outdir == NULL;
}
struct hls_sub_segmenter;
sout_mux_t *CreateSubtitleSegmenter(sout_access_out_t *access,
const struct hls_config *config);
void hls_sub_segmenter_SignalStreamUpdate(sout_mux_t *, vlc_tick_t);
#endif
......@@ -42,12 +42,33 @@ static void hls_segment_Destroy(hls_segment_t *segment)
free(segment);
}
static const char *
hls_segment_queue_GetFileExtension(enum hls_playlist_type type)
{
switch (type)
{
case HLS_PLAYLIST_TYPE_TS:
return "ts";
case HLS_PLAYLIST_TYPE_WEBVTT:
return "vtt";
default:
vlc_assert_unreachable();
}
}
void hls_segment_queue_Init(hls_segment_queue_t *queue,
const struct hls_segment_queue_config *config,
const struct hls_config *hls_config)
{
memcpy(&queue->config, config, sizeof(*config));
queue->playlist_id = config->playlist_id;
queue->total_segments = 0;
queue->httpd_ref = config->httpd_ref;
queue->httpd_callback = config->httpd_callback;
queue->file_extension =
hls_segment_queue_GetFileExtension(config->playlist_type);
queue->hls_config = hls_config;
vlc_list_init(&queue->segments);
......@@ -71,10 +92,11 @@ int hls_segment_queue_NewSegment(hls_segment_queue_t *queue,
segment->length = length;
if (asprintf(&segment->url,
"%s/playlist-%u-%u.ts",
"%s/playlist-%u-%u.%s",
queue->hls_config->base_url,
queue->config.playlist_id,
segment->id) == -1)
queue->playlist_id,
segment->id,
queue->file_extension) == -1)
{
segment->url = NULL;
goto nomem;
......@@ -89,16 +111,16 @@ int hls_segment_queue_NewSegment(hls_segment_queue_t *queue,
if (unlikely(segment->storage == NULL))
goto nomem;
if (queue->config.httpd_ref != NULL)
if (queue->httpd_ref != NULL)
{
segment->http_url =
httpd_UrlNew(queue->config.httpd_ref, segment->url, NULL, NULL);
httpd_UrlNew(queue->httpd_ref, segment->url, NULL, NULL);
if (segment->http_url == NULL)
goto nomem;
httpd_UrlCatch(segment->http_url,
HTTPD_MSG_GET,
queue->config.httpd_callback,
queue->httpd_callback,
(httpd_callback_sys_t *)segment->storage);
}
else
......
......@@ -39,6 +39,7 @@ typedef struct hls_segment
struct hls_segment_queue_config
{
unsigned int playlist_id;
enum hls_playlist_type playlist_type;
httpd_host_t *httpd_ref;
httpd_callback_t httpd_callback;
......@@ -46,9 +47,14 @@ struct hls_segment_queue_config
typedef struct
{
struct hls_segment_queue_config config;
unsigned int playlist_id;
unsigned int total_segments;
httpd_host_t *httpd_ref;
httpd_callback_t httpd_callback;
const char *file_extension;
const struct hls_config *hls_config;
struct vlc_list segments;
......
/*****************************************************************************
* subtitle_segmenter.c: Create subtitle segments
*****************************************************************************
* Copyright (C) 2024 VLC authors and VideoLAN
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser 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 <vlc_common.h>
#include <vlc_block.h>
#include <vlc_frame.h>
#include <vlc_sout.h>
#include <vlc_tick.h>
#include "hls.h"
/**
* The hls_sub_segmenter is a meta-muxer used to create subtitles segments. It
* handles subtitle frames splitting, re-creation of the subtitles muxer and
* empty segments creation when no data is available.
*/
struct hls_sub_segmenter
{
sout_mux_t owner;
const struct hls_config *config;
sout_mux_t *spu_muxer;
sout_input_t *spu_muxer_input;
bool segment_empty;
vlc_tick_t last_segment;
vlc_tick_t last_stream_update;
};
static int hls_sub_segmenter_ResetMuxer(struct hls_sub_segmenter *segmenter,
sout_input_t *owner_input)
{
if (segmenter->spu_muxer_input != NULL)
sout_MuxDeleteStream(segmenter->spu_muxer, segmenter->spu_muxer_input);
if (segmenter->spu_muxer != NULL)
sout_MuxDelete(segmenter->spu_muxer);
segmenter->segment_empty = true;
segmenter->spu_muxer = sout_MuxNew(segmenter->owner.p_access, "webvtt");
if (segmenter->spu_muxer == NULL)
return VLC_EGENERIC;
segmenter->spu_muxer_input =
sout_MuxAddStream(segmenter->spu_muxer, &owner_input->fmt);
// Disable mux caching.
segmenter->spu_muxer->b_waiting_stream = false;
return VLC_SUCCESS;
}
static int hls_sub_segmenter_EndSegment(struct hls_sub_segmenter *segmenter,
sout_input_t *input)
{
segmenter->last_segment += segmenter->config->segment_length;
return hls_sub_segmenter_ResetMuxer(segmenter, input);
}
static int hls_sub_segmenter_EndEmptySegment(
struct hls_sub_segmenter *segmenter, sout_input_t *input, vlc_tick_t len)
{
char *vtt_header = strdup("WEBVTT\n\n");
if (unlikely(vtt_header == NULL))
return VLC_ENOMEM;
block_t *empty_segment = block_heap_Alloc(vtt_header, strlen(vtt_header));
if (unlikely(empty_segment == NULL))
return VLC_ENOMEM;
empty_segment->i_flags |= BLOCK_FLAG_HEADER;
empty_segment->i_length = len;
if (sout_AccessOutWrite(segmenter->spu_muxer->p_access, empty_segment) < 0)
return VLC_EGENERIC;
return hls_sub_segmenter_EndSegment(segmenter, input);
}
static int hls_sub_segmenter_Add(sout_mux_t *mux, sout_input_t *input)
{
struct hls_sub_segmenter *segmenter = mux->p_sys;
hls_sub_segmenter_ResetMuxer(segmenter, input);
return VLC_SUCCESS;
}
static int hls_sub_segmenter_MuxSend(struct hls_sub_segmenter *segmenter,
vlc_frame_t *spu)
{
segmenter->segment_empty = false;
return sout_MuxSendBuffer(
segmenter->spu_muxer, segmenter->spu_muxer_input, spu);
}
static void hls_sub_segmenter_Del(sout_mux_t *mux, sout_input_t *input)
{
struct hls_sub_segmenter *segmenter = mux->p_sys;
if (segmenter->last_segment != segmenter->last_stream_update)
{
const vlc_tick_t seglen =
segmenter->last_stream_update - segmenter->last_segment;
if (!vlc_fifo_IsEmpty(input->p_fifo))
{
/* Drain the last ephemeral SPU. */
vlc_frame_t *ephemeral = vlc_fifo_Get(input->p_fifo);
assert(ephemeral->i_length == VLC_TICK_INVALID);
ephemeral->i_length = seglen;
hls_sub_segmenter_MuxSend(segmenter, ephemeral);
}
else if (segmenter->segment_empty)
{
/* Drain an empty last segment. */
hls_sub_segmenter_EndEmptySegment(segmenter, input, seglen);
}
}
if (segmenter->spu_muxer_input != NULL)
sout_MuxDeleteStream(segmenter->spu_muxer, segmenter->spu_muxer_input);
if (segmenter->spu_muxer != NULL)
sout_MuxDelete(segmenter->spu_muxer);
}
static vlc_frame_t* hls_sub_segmenter_CutEphemeral(vlc_frame_t *ephemeral, vlc_tick_t length)
{
vlc_frame_t *cut = vlc_frame_Duplicate(ephemeral);
if (unlikely(cut == NULL))
return NULL;
cut->i_length = length;
ephemeral->i_pts += cut->i_length;
return cut;
}
/**
* Should be called at frequent PCR interval.
*
* Creates empty segments when no data is sent to the muxer for a time superior
* to segment length.
*/
void hls_sub_segmenter_SignalStreamUpdate(sout_mux_t *mux,
vlc_tick_t stream_time)
{
if (mux->i_nb_inputs == 0)
return;
sout_input_t *owner_input = mux->pp_inputs[0];
struct hls_sub_segmenter *segmenter = mux->p_sys;
segmenter->last_stream_update = stream_time;
if (stream_time - segmenter->last_segment <
segmenter->config->segment_length)
return;
if (!vlc_fifo_IsEmpty(owner_input->p_fifo))
{
vlc_frame_t *ephemeral = vlc_fifo_Show(owner_input->p_fifo);
/* The segmenter should only keep ephemeral SPUs in queue. */
assert(ephemeral->i_length == VLC_TICK_INVALID);
const vlc_tick_t eph_len =
(segmenter->last_segment + segmenter->config->segment_length) -
ephemeral->i_pts;
vlc_frame_t *cut = hls_sub_segmenter_CutEphemeral(ephemeral, eph_len);
if (likely(cut != NULL))
hls_sub_segmenter_MuxSend(segmenter, cut);
}
if (!segmenter->segment_empty)
{
hls_sub_segmenter_EndSegment(segmenter, owner_input);
return;
}
hls_sub_segmenter_EndEmptySegment(
segmenter, owner_input, segmenter->config->segment_length);
}
static int
hls_sub_segmenter_ProcessSingleFrame(struct hls_sub_segmenter *segmenter,
sout_input_t *owner_input,
vlc_frame_t *spu)
{
const vlc_tick_t seglen = segmenter->config->segment_length;
if (spu->i_pts >= segmenter->last_segment + seglen)
hls_sub_segmenter_EndSegment(segmenter, owner_input);
// If the subtitle overlaps between segments, we have to split it.
while (spu->i_pts + spu->i_length > segmenter->last_segment + seglen)
{
vlc_frame_t *capped_spu = vlc_frame_Duplicate(spu);
if (unlikely(capped_spu == NULL))
{
vlc_frame_Release(spu);
return VLC_ENOMEM;
}
// Extra time not fitting in the current segment.
const vlc_tick_t extra_time =
(spu->i_pts - VLC_TICK_0 + spu->i_length) -
segmenter->last_segment - seglen;
// Shorten the copy length so it fits the current segment.
capped_spu->i_length -= extra_time;
if (spu->i_dts != VLC_TICK_INVALID)
spu->i_dts += capped_spu->i_length;
spu->i_pts += capped_spu->i_length;
spu->i_length = extra_time;
const int status = hls_sub_segmenter_MuxSend(segmenter, capped_spu);
if (status != VLC_SUCCESS)
{
vlc_frame_Release(spu);
return status;
}
hls_sub_segmenter_EndSegment(segmenter, owner_input);
}
return hls_sub_segmenter_MuxSend(segmenter, spu);
}
static int hls_sub_segmenter_Process(sout_mux_t *mux)
{
struct hls_sub_segmenter *segmenter = mux->p_sys;
if (mux->i_nb_inputs == 0)
return VLC_SUCCESS;
sout_input_t *input = mux->pp_inputs[0];
vlc_fifo_Lock(input->p_fifo);
vlc_frame_t *chain = vlc_fifo_DequeueAllUnlocked(input->p_fifo);
int status = VLC_SUCCESS;
while (chain != NULL)
{
if (chain->i_length == VLC_TICK_INVALID)
{
/* Ephemeral SPUs length depends on the next SPUs timestamps. */
if (chain->p_next == NULL)
{
vlc_fifo_QueueUnlocked(input->p_fifo, chain);
break;
}
else
{
chain->i_length = chain->p_next->i_pts - chain->i_pts;
}
}
vlc_frame_t *next = chain->p_next;
chain->p_next = NULL;
status =
hls_sub_segmenter_ProcessSingleFrame(segmenter, input, chain);
if (status != VLC_SUCCESS)
{
vlc_frame_ChainRelease(next);
break;
}
chain = next;
}
vlc_fifo_Unlock(input->p_fifo);
return status;
}
static int hls_sub_segmenter_Control(sout_mux_t *mux, int query, va_list args)
{
if (query != MUX_CAN_ADD_STREAM_WHILE_MUXING)
return VLC_ENOTSUP;
*(va_arg(args, bool *)) = false;
return VLC_SUCCESS;
(void)mux;
}
sout_mux_t *CreateSubtitleSegmenter(sout_access_out_t *access,
const struct hls_config *config)
{
struct hls_sub_segmenter *segmenter =
vlc_object_create(access, sizeof(*segmenter));
if (unlikely(segmenter == NULL))
return NULL;
sout_mux_t *mux = &segmenter->owner;
mux->psz_mux = strdup("hls-sub-segmenter");
if (unlikely(mux->psz_mux == NULL))
{
vlc_object_delete(mux);
return NULL;
}
mux->p_module = NULL;
mux->p_cfg = NULL;
mux->i_nb_inputs = 0;
mux->pp_inputs = NULL;
mux->b_add_stream_any_time = false;
mux->b_waiting_stream = true;
mux->i_add_stream_start = VLC_TICK_INVALID;
mux->p_sys = segmenter;
mux->p_access = access;
mux->pf_addstream = hls_sub_segmenter_Add;
mux->pf_delstream = hls_sub_segmenter_Del;
mux->pf_mux = hls_sub_segmenter_Process;
mux->pf_control = hls_sub_segmenter_Control;
segmenter->config = config;
segmenter->spu_muxer = NULL;
segmenter->segment_empty = true;
segmenter->last_segment = VLC_TICK_0;
segmenter->last_stream_update = VLC_TICK_INVALID;
return &segmenter->owner;
}
......@@ -57,6 +57,7 @@ check_PROGRAMS = \
test_modules_tls \
test_modules_stream_out_transcode \
test_modules_mux_webvtt \
test_modules_stream_out_hls_subtitles_segmenter \
$(NULL)
if HAVE_GL
......@@ -303,6 +304,12 @@ test_modules_stream_out_pcr_sync_LDADD = $(LIBVLCCORE)
test_modules_mux_webvtt_SOURCES = modules/mux/webvtt.c
test_modules_mux_webvtt_LDADD = $(LIBVLCCORE) $(LIBVLC)
test_modules_stream_out_hls_subtitles_segmenter_SOURCES = \
modules/stream_out/hls/subtitles_segmenter.c \
../modules/stream_out/hls/hls.h \
../modules/stream_out/hls/subtitles_segmenter.c
test_modules_stream_out_hls_subtitles_segmenter_LDADD = $(LIBVLCCORE) $(LIBVLC)
checkall:
$(MAKE) check_PROGRAMS="$(check_PROGRAMS) $(EXTRA_PROGRAMS)" check
......
/*****************************************************************************
* subtitles_segmenter.c: HLS subtitle segmentation unit tests
*****************************************************************************
* Copyright (C) 2024 VLC authors and VideoLAN
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser 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 <vlc_common.h>
#include <vlc_block.h>
#include <vlc_plugin.h>
#include <vlc_sout.h>
#include "../../../libvlc/test.h"
#include "../../modules/stream_out/hls/hls.h"
#include "../lib/libvlc_internal.h"
struct test_scenario
{
vlc_tick_t segment_length;
unsigned bytes_reported;
const char *expected;
void (*report_sub)(block_t *);
void (*send_sub)(sout_mux_t *, sout_input_t *);
};
static struct test_scenario *CURRENT_SCENARIO = NULL;
static ssize_t AccessOutWrite(sout_access_out_t *access, block_t *block)
{
struct test_scenario *cb = access->p_sys;
assert(block->p_next == NULL);
cb->report_sub(block);
const ssize_t r = block->i_buffer;
block_ChainRelease(block);
return r;
}
static sout_access_out_t *CreateAccessOut(vlc_object_t *parent,
const struct test_scenario *cb)
{
sout_access_out_t *access = vlc_object_create(parent, sizeof(*access));
if (unlikely(access == NULL))
return NULL;
access->psz_access = strdup("mock");
if (unlikely(access->psz_access == NULL))
{
vlc_object_delete(access);
return NULL;
}
access->p_cfg = NULL;
access->p_module = NULL;
access->p_sys = (void *)cb;
access->psz_path = NULL;
access->pf_control = NULL;
access->pf_read = NULL;
access->pf_seek = NULL;
access->pf_write = AccessOutWrite;
return access;
}
static vlc_frame_t *
make_spu(const char *text, vlc_tick_t start, vlc_tick_t stop)
{
char *owned_txt = strdup(text);
assert(owned_txt != NULL);
vlc_frame_t *spu = vlc_frame_heap_Alloc(owned_txt, strlen(owned_txt));
spu->i_pts = spu->i_dts = start + VLC_TICK_0;
if (stop != VLC_TICK_INVALID)
spu->i_length = stop - start;
else
spu->i_length = VLC_TICK_INVALID;
return spu;
}
static vlc_frame_t *make_ephemer_spu(const char *text, vlc_tick_t start)
{
return make_spu(text, start, VLC_TICK_INVALID);
}
#define NO_SPLIT_EXPECTED \
"WEBVTT\n\n" \
"00:00:00.000 --> 00:00:03.000\n" \
"Hello world\n\n"
static void send_no_split(sout_mux_t *mux, sout_input_t *input)
{
vlc_frame_t *spu = make_spu("Hello world", 0, VLC_TICK_FROM_SEC(3));
const int status = sout_MuxSendBuffer(mux, input, spu);
assert(status == VLC_SUCCESS);
}
#define ONE_SPLIT_EXPECTED \
"WEBVTT\n\n" \
"00:00:00.000 --> 00:00:04.000\n" \
"Hello world\n\n" \
"WEBVTT\n\n" \
"00:00:04.000 --> 00:00:06.000\n" \
"Hello world\n\n"
static void send_one_split(sout_mux_t *mux, sout_input_t *input)
{
vlc_frame_t *spu = make_spu("Hello world", 0, VLC_TICK_FROM_SEC(6));
const int status = sout_MuxSendBuffer(mux, input, spu);
assert(status == VLC_SUCCESS);
}
#define FIVE_SPLITS_EXPECTED \
"WEBVTT\n\n" \
"00:00:01.500 --> 00:00:02.000\n" \
"Hello world\n\n" \
"WEBVTT\n\n" \
"00:00:02.000 --> 00:00:04.000\n" \
"Hello world\n\n" \
"WEBVTT\n\n" \
"00:00:04.000 --> 00:00:06.000\n" \
"Hello world\n\n" \
"WEBVTT\n\n" \
"00:00:06.000 --> 00:00:08.000\n" \
"Hello world\n\n" \
"WEBVTT\n\n" \
"00:00:08.000 --> 00:00:09.500\n" \
"Hello world\n\n"
static void send_five_splits(sout_mux_t *mux, sout_input_t *input)
{
vlc_frame_t *spu =
make_spu("Hello world", VLC_TICK_FROM_MS(1500), VLC_TICK_FROM_MS(9500));
const int status = sout_MuxSendBuffer(mux, input, spu);
assert(status == VLC_SUCCESS);
}
#define NOTHING_FOR_FIVE_SEC_EXPECTED \
"WEBVTT\n\n" \
"WEBVTT\n\n" \
"WEBVTT\n\n"
static void send_nothing_for_five_sec(sout_mux_t *mux, sout_input_t *input)
{
for (vlc_tick_t stream_time = VLC_TICK_0;
stream_time < VLC_TICK_FROM_MS(5300);
stream_time += VLC_TICK_FROM_MS(150))
{
hls_sub_segmenter_SignalStreamUpdate(mux, stream_time);
}
(void)input;
}
#define BEGIN_HOLE_EXPECTED \
"WEBVTT\n\n" \
"WEBVTT\n\n" \
"WEBVTT\n\n" \
"WEBVTT\n\n" \
"00:00:07.500 --> 00:00:08.000\n" \
"Hello world\n\n" \
"WEBVTT\n\n" \
"00:00:08.300 --> 00:00:10.000\n" \
"Hello world\n\n" \
"WEBVTT\n\n" \
"00:00:10.000 --> 00:00:11.500\n" \
"Hello world\n\n"
static void send_begin_hole(sout_mux_t *mux, sout_input_t *input)
{
for (vlc_tick_t stream_time = VLC_TICK_0;
stream_time < VLC_TICK_FROM_MS(6150);
stream_time += VLC_TICK_FROM_MS(150))
{
hls_sub_segmenter_SignalStreamUpdate(mux, stream_time);
}
vlc_frame_t *spu =
make_spu("Hello world", VLC_TICK_FROM_MS(7500), VLC_TICK_FROM_MS(8000));
int status = sout_MuxSendBuffer(mux, input, spu);
assert(status == VLC_SUCCESS);
spu = make_spu(
"Hello world", VLC_TICK_FROM_MS(8300), VLC_TICK_FROM_MS(11500));
status = sout_MuxSendBuffer(mux, input, spu);
assert(status == VLC_SUCCESS);
}
#define MIDDLE_HOLE_EXPECTED \
"WEBVTT\n\n" \
"00:00:01.500 --> 00:00:02.000\n" \
"Hello world\n\n" \
"WEBVTT\n\n" \
"00:00:02.000 --> 00:00:02.300\n" \
"Hello world\n\n" \
"00:00:02.300 --> 00:00:03.000\n" \
"Hello world\n\n" \
"WEBVTT\n\n" \
"WEBVTT\n\n" \
"WEBVTT\n\n" \
"00:00:11.000 --> 00:00:11.500\n" \
"Hello world\n\n"
static void send_middle_hole(sout_mux_t *mux, sout_input_t *input)
{
vlc_frame_t *spu =
make_spu("Hello world", VLC_TICK_FROM_MS(1500), VLC_TICK_FROM_MS(2300));
int status = sout_MuxSendBuffer(mux, input, spu);
assert(status == VLC_SUCCESS);
spu =
make_spu("Hello world", VLC_TICK_FROM_MS(2300), VLC_TICK_FROM_MS(3000));
status = sout_MuxSendBuffer(mux, input, spu);
assert(status == VLC_SUCCESS);
for (vlc_tick_t stream_time = VLC_TICK_FROM_MS(3000) + VLC_TICK_0;
stream_time < VLC_TICK_FROM_MS(10000);
stream_time += VLC_TICK_FROM_MS(150))
{
hls_sub_segmenter_SignalStreamUpdate(mux, stream_time);
}
spu = make_spu(
"Hello world", VLC_TICK_FROM_MS(11000), VLC_TICK_FROM_MS(11500));
status = sout_MuxSendBuffer(mux, input, spu);
assert(status == VLC_SUCCESS);
}
#define END_HOLE_EXPECTED \
"WEBVTT\n\n" \
"00:00:01.500 --> 00:00:02.000\n" \
"Hello world\n\n" \
"WEBVTT\n\n" \
"00:00:02.000 --> 00:00:02.300\n" \
"Hello world\n\n" \
"00:00:02.300 --> 00:00:03.000\n" \
"Hello world\n\n" \
"WEBVTT\n\n" \
"WEBVTT\n\n" \
"WEBVTT\n\n"
static void send_end_hole(sout_mux_t *mux, sout_input_t *input)
{
vlc_frame_t *spu =
make_spu("Hello world", VLC_TICK_FROM_MS(1500), VLC_TICK_FROM_MS(2300));
int status = sout_MuxSendBuffer(mux, input, spu);
assert(status == VLC_SUCCESS);
spu =
make_spu("Hello world", VLC_TICK_FROM_MS(2300), VLC_TICK_FROM_MS(3000));
status = sout_MuxSendBuffer(mux, input, spu);
assert(status == VLC_SUCCESS);
for (vlc_tick_t stream_time = VLC_TICK_FROM_MS(3000) + VLC_TICK_0;
stream_time < VLC_TICK_FROM_MS(10000);
stream_time += VLC_TICK_FROM_MS(150))
{
hls_sub_segmenter_SignalStreamUpdate(mux, stream_time);
}
}
#define EPHEMER_NO_SPLIT_EXPECTED \
"WEBVTT\n\n" \
"00:00:01.500 --> 00:00:02.000\n" \
"Hello world\n\n" \
"WEBVTT\n\n" \
"00:00:02.000 --> 00:00:02.300\n" \
"Hello world\n\n" \
"00:00:02.300 --> 00:00:03.100\n" \
"Hello world\n\n" \
"00:00:03.100 --> 00:00:04.000\n" \
"Hello world\n\n" \
"WEBVTT\n\n" \
"00:00:04.000 --> 00:00:05.500\n" \
"Hello world\n\n"
static void send_ephemer_no_split(sout_mux_t *mux, sout_input_t *input)
{
vlc_frame_t *spu = make_ephemer_spu("Hello world", VLC_TICK_FROM_MS(1500));
int status = sout_MuxSendBuffer(mux, input, spu);
assert(status == VLC_SUCCESS);
spu = make_ephemer_spu("Hello world", VLC_TICK_FROM_MS(2300));
status = sout_MuxSendBuffer(mux, input, spu);
assert(status == VLC_SUCCESS);
hls_sub_segmenter_SignalStreamUpdate(mux,
VLC_TICK_FROM_MS(2300) + VLC_TICK_0);
spu = make_ephemer_spu("Hello world", VLC_TICK_FROM_MS(3100));
status = sout_MuxSendBuffer(mux, input, spu);
assert(status == VLC_SUCCESS);
hls_sub_segmenter_SignalStreamUpdate(mux,
VLC_TICK_FROM_MS(3100) + VLC_TICK_0);
spu = make_ephemer_spu("Hello world", VLC_TICK_FROM_MS(4000));
status = sout_MuxSendBuffer(mux, input, spu);
assert(status == VLC_SUCCESS);
hls_sub_segmenter_SignalStreamUpdate(mux,
VLC_TICK_FROM_MS(4000) + VLC_TICK_0);
hls_sub_segmenter_SignalStreamUpdate(mux,
VLC_TICK_FROM_MS(5500) + VLC_TICK_0);
}
#define LONG_EPHEMER_EXPECTED \
"WEBVTT\n\n" \
"00:00:01.500 --> 00:00:02.000\n" \
"Hello world\n\n" \
"WEBVTT\n\n" \
"00:00:02.000 --> 00:00:04.000\n" \
"Hello world\n\n" \
"WEBVTT\n\n" \
"00:00:04.000 --> 00:00:06.000\n" \
"Hello world\n\n" \
"WEBVTT\n\n" \
"00:00:06.000 --> 00:00:08.000\n" \
"Hello world\n\n" \
"WEBVTT\n\n" \
"00:00:08.000 --> 00:00:09.500\n" \
"Hello world\n\n"
static void send_long_ephemer(sout_mux_t *mux, sout_input_t *input)
{
vlc_frame_t *spu = make_ephemer_spu("Hello world", VLC_TICK_FROM_MS(1500));
int status = sout_MuxSendBuffer(mux, input, spu);
assert(status == VLC_SUCCESS);
for (vlc_tick_t stream_time = VLC_TICK_FROM_MS(1500);
stream_time <= VLC_TICK_FROM_MS(9500);
stream_time += VLC_TICK_FROM_MS(100))
{
hls_sub_segmenter_SignalStreamUpdate(mux, stream_time + VLC_TICK_0);
}
}
static void default_report(block_t *sub)
{
const char *expected =
CURRENT_SCENARIO->expected + CURRENT_SCENARIO->bytes_reported;
const int cmp =
strncmp(expected, (const char *)sub->p_buffer, sub->i_buffer);
assert(cmp == 0);
CURRENT_SCENARIO->bytes_reported += sub->i_buffer;
}
static struct test_scenario TEST_SCENARIOS[] = {
{
.segment_length = VLC_TICK_FROM_SEC(4),
.send_sub = send_no_split,
.report_sub = default_report,
.expected = NO_SPLIT_EXPECTED,
},
{
.segment_length = VLC_TICK_FROM_SEC(4),
.send_sub = send_one_split,
.report_sub = default_report,
.expected = ONE_SPLIT_EXPECTED,
},
{
.segment_length = VLC_TICK_FROM_SEC(2),
.send_sub = send_five_splits,
.report_sub = default_report,
.expected = FIVE_SPLITS_EXPECTED,
},
{
.segment_length = VLC_TICK_FROM_SEC(2),
.send_sub = send_nothing_for_five_sec,
.report_sub = default_report,
.expected = NOTHING_FOR_FIVE_SEC_EXPECTED,
},
{
.segment_length = VLC_TICK_FROM_SEC(2),
.send_sub = send_begin_hole,
.report_sub = default_report,
.expected = BEGIN_HOLE_EXPECTED,
},
{
.segment_length = VLC_TICK_FROM_SEC(2),
.send_sub = send_middle_hole,
.report_sub = default_report,
.expected = MIDDLE_HOLE_EXPECTED,
},
{
.segment_length = VLC_TICK_FROM_SEC(2),
.send_sub = send_end_hole,
.report_sub = default_report,
.expected = END_HOLE_EXPECTED,
},
{
.segment_length = VLC_TICK_FROM_SEC(2),
.send_sub = send_ephemer_no_split,
.report_sub = default_report,
.expected = EPHEMER_NO_SPLIT_EXPECTED,
},
{
.segment_length = VLC_TICK_FROM_SEC(2),
.send_sub = send_long_ephemer,
.report_sub = default_report,
.expected = LONG_EPHEMER_EXPECTED,
},
};
static void RunTests(libvlc_instance_t *instance)
{
for (size_t i = 0; i < ARRAY_SIZE(TEST_SCENARIOS); ++i)
{
CURRENT_SCENARIO = &TEST_SCENARIOS[i];
sout_access_out_t *access = CreateAccessOut(
VLC_OBJECT(instance->p_libvlc_int), CURRENT_SCENARIO);
assert(access != NULL);
const struct hls_config config = {
.segment_length = CURRENT_SCENARIO->segment_length,
};
sout_mux_t *mux = CreateSubtitleSegmenter(access, &config);
assert(mux != NULL);
es_format_t fmt;
es_format_Init(&fmt, SPU_ES, VLC_CODEC_TEXT);
sout_input_t *input = sout_MuxAddStream(mux, &fmt);
assert(input != NULL);
// Disable mux caching.
mux->b_waiting_stream = false;
CURRENT_SCENARIO->send_sub(mux, input);
sout_MuxDeleteStream(mux, input);
assert(CURRENT_SCENARIO->bytes_reported ==
strlen(CURRENT_SCENARIO->expected));
sout_MuxDelete(mux);
sout_AccessOutDelete(access);
}
}
int main(void)
{
test_init();
const char *const args[] = {
"-vvv",
};
libvlc_instance_t *vlc = libvlc_new(ARRAY_SIZE(args), args);
if (vlc == NULL)
return 1;
RunTests(vlc);
libvlc_release(vlc);
}