You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

650 lines
22 KiB

/*****************************************************************************
*
*****************************************************************************
* Copyright (C) 2020 VideoLabs, VideoLAN and VLC Authors
*
* 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 "../../plumbing/FakeESOut.hpp"
#include "../../plumbing/FakeESOutID.hpp"
#include "../../plumbing/CommandsQueue.hpp"
#include "../test.hpp"
#include <vlc_block.h>
#include <vlc_es_out.h>
#include <limits>
#include <list>
#include <algorithm>
#include <cassert>
using namespace adaptive;
using OutputVal = std::pair<const AbstractFakeESOutID *, block_t *>;
const Times drainTimes(SegmentTimes(),std::numeric_limits<vlc_tick_t>::max());
#define DT(t) Times(SegmentTimes(), (t))
class DummyEsOut
{
public:
DummyEsOut();
~DummyEsOut();
void reset();
static es_out_id_t *callback_add( es_out_t *, input_source_t *, const es_format_t * );
static int callback_send( es_out_t *, es_out_id_t *, block_t * );
static void callback_del( es_out_t *, es_out_id_t * );
static int callback_control( es_out_t *, input_source_t *, int, va_list );
static void callback_destroy( es_out_t * );
vlc_tick_t dts;
vlc_tick_t pts;
vlc_tick_t pcr;
class ES
{
public:
ES(const es_format_t *);
~ES();
es_format_t fmt;
bool b_selected;
};
std::list<ES *> eslist;
};
DummyEsOut::DummyEsOut()
{
reset();
}
DummyEsOut::~DummyEsOut()
{
}
void DummyEsOut::reset()
{
dts = VLC_TICK_INVALID;
pts = VLC_TICK_INVALID;
pcr = VLC_TICK_INVALID;
while(!eslist.empty())
{
delete eslist.front();
eslist.pop_front();
}
}
struct dropesout
{
DummyEsOut *dummyesout;
es_out_t esout;
};
es_out_id_t *DummyEsOut::callback_add(es_out_t *out, input_source_t *, const es_format_t *fmt)
{
DummyEsOut *dummyesout = container_of(out, dropesout, esout)->dummyesout;
ES *es = new ES(fmt);
dummyesout->eslist.push_back(es);
return (es_out_id_t *) es;
}
int DummyEsOut::callback_send(es_out_t *out, es_out_id_t *, block_t *b)
{
DummyEsOut *dummyesout = container_of(out, dropesout, esout)->dummyesout;
dummyesout->dts = b->i_dts;
dummyesout->pts = b->i_pts;
block_Release(b);
return VLC_SUCCESS;
}
void DummyEsOut::callback_del(es_out_t *out, es_out_id_t *id)
{
DummyEsOut *dummyesout = container_of(out, dropesout, esout)->dummyesout;
ES *es = (ES *) id;
auto it = std::find(dummyesout->eslist.begin(), dummyesout->eslist.end(), es);
assert(it != dummyesout->eslist.end());
if(it != dummyesout->eslist.end())
dummyesout->eslist.erase(it);
delete es;
}
int DummyEsOut::callback_control(es_out_t *out, input_source_t *, int i_query, va_list args)
{
DummyEsOut *dummyesout = container_of(out, dropesout, esout)->dummyesout;
switch( i_query )
{
case ES_OUT_SET_PCR:
case ES_OUT_SET_GROUP_PCR:
{
if( i_query == ES_OUT_SET_GROUP_PCR )
(void) va_arg( args, int );
dummyesout->pcr = va_arg( args, vlc_tick_t );
break;
}
case ES_OUT_SET_ES:
{
ES *es = (ES *) va_arg( args, es_out_id_t * );
/* emulate reselection */
for( ES *e : dummyesout->eslist )
{
if( e->fmt.i_cat == es->fmt.i_cat )
e->b_selected = (e == es);
}
return VLC_SUCCESS;
}
case ES_OUT_SET_ES_STATE:
{
/* emulate selection override */
ES *es = (ES *) va_arg( args, es_out_id_t * );
bool b = va_arg( args, int );
auto it = std::find(dummyesout->eslist.begin(), dummyesout->eslist.end(), es);
if(it == dummyesout->eslist.end())
return VLC_EGENERIC;
(*it)->b_selected = b;
return VLC_SUCCESS;
}
case ES_OUT_GET_ES_STATE:
{
ES *es = (ES *) va_arg( args, es_out_id_t * );
auto it = std::find(dummyesout->eslist.begin(), dummyesout->eslist.end(), es);
if(it == dummyesout->eslist.end())
return VLC_EGENERIC;
*va_arg( args, bool * ) = (*it)->b_selected;
return VLC_SUCCESS;
}
default:
return VLC_EGENERIC;
}
return VLC_SUCCESS;
}
void DummyEsOut::callback_destroy(es_out_t *)
{
}
static const struct es_out_callbacks dummycbs = []() constexpr
{
es_out_callbacks cbs = {};
cbs.add = DummyEsOut::callback_add;
cbs.send = DummyEsOut::callback_send;
cbs.del = DummyEsOut::callback_del;
cbs.control = DummyEsOut::callback_control;
cbs.destroy = DummyEsOut::callback_destroy;
return cbs;
}();
DummyEsOut::ES::ES(const es_format_t *src)
{
b_selected = false;
es_format_Init(&fmt, src->i_cat, src->i_codec);
es_format_Copy(&fmt, src);
}
DummyEsOut::ES::~ES()
{
es_format_Clean(&fmt);
}
static void enqueue(es_out_t *out, es_out_id_t *id, vlc_tick_t dts, vlc_tick_t pts)
{
block_t *b = block_Alloc(1);
if(b)
{
b->i_dts = dts;
b->i_pts = pts;
es_out_Send(out, id, b);
}
}
#define TMS(t) (VLC_TICK_0 + VLC_TICK_FROM_MS(t))
#define DMS(t) (VLC_TICK_FROM_MS(t))
#define SEND(t) enqueue(out, id, t, t)
#define PCR(t) es_out_SetPCR(out, t)
#define FROM_MPEGTS(x) (INT64_C(x) * 100 / 9)
static int check3(es_out_t *out, DummyEsOut *dummy, FakeESOut *fakees)
{
es_format_t fmt;
/* few ES reusability checks */
try
{
es_format_Init(&fmt, AUDIO_ES, VLC_CODEC_MP4A);
FakeESOutID fakeid0(fakees, &fmt);
fmt.audio.i_rate = 48000;
FakeESOutID fakeid1(fakees, &fmt);
fmt.i_original_fourcc = 0xbeef;
FakeESOutID fakeid2(fakees, &fmt);
es_format_Clean(&fmt);
es_format_Init(&fmt, VIDEO_ES, VLC_CODEC_H264);
FakeESOutID fakeid3(fakees, &fmt);
fmt.i_codec = VLC_CODEC_MPGV;
FakeESOutID fakeid4(fakees, &fmt);
fakees->setSrcID(SrcID::make()); // change source sequence id
FakeESOutID fakeid0b(fakees, fakeid0.getFmt());
FakeESOutID fakeid4b(fakees, fakeid4.getFmt());
Expect(fakeid0.isCompatible(&fakeid0) == true); // aac without rate, same source
Expect(fakeid0.isCompatible(&fakeid1) == false); // aac rate/unknown mix
Expect(fakeid1.isCompatible(&fakeid1) == true); // aac with same rate
Expect(fakeid0.isCompatible(&fakeid3) == false); // different codecs
Expect(fakeid1.isCompatible(&fakeid2) == false); // different original fourcc
Expect(fakeid2.isCompatible(&fakeid2) == true); // same original fourcc
Expect(fakeid3.isCompatible(&fakeid3) == false); // same video with extra codecs
Expect(fakeid0.isCompatible(&fakeid0b) == false); // aac without rate, different source
Expect(fakeid4.isCompatible(&fakeid4) == true); // same codec, same sequence
Expect(fakeid4.isCompatible(&fakeid4b) == false); // same codec, different sequence
es_format_Clean(&fmt);
} catch (...) {
return 1;
}
es_format_Init(&fmt, VIDEO_ES, VLC_CODEC_H264);
es_out_id_t *id = es_out_Add(out, &fmt);
try
{
Expect(id != nullptr);
/* single ES should be allocated */
Expect(dummy->eslist.size() == 0);
fakees->commandsQueue()->Commit();
fakees->commandsQueue()->Process(drainTimes);
Expect(dummy->eslist.size() == 1);
Expect(dummy->eslist.front()->fmt.i_codec == VLC_CODEC_H264);
dummy->eslist.front()->b_selected = true; /* fake selection */
/* subsequent ES should be allocated */
es_format_Clean(&fmt);
es_format_Init(&fmt, AUDIO_ES, VLC_CODEC_MP4A);
id = es_out_Add(out, &fmt);
fakees->commandsQueue()->Commit();
fakees->commandsQueue()->Process(drainTimes);
Expect(dummy->eslist.size() == 2);
Expect(dummy->eslist.front()->fmt.i_codec == VLC_CODEC_H264);
Expect(dummy->eslist.front()->b_selected == true);
Expect(dummy->eslist.back()->fmt.i_codec == VLC_CODEC_MP4A);
/* on restart / new segment, unused ES should be reclaimed */
fakees->recycleAll();
es_format_Clean(&fmt);
es_format_Init(&fmt, VIDEO_ES, VLC_CODEC_MPGV);
id = es_out_Add(out, &fmt);
fakees->commandsQueue()->Commit();
fakees->commandsQueue()->Process(drainTimes);
Expect(dummy->eslist.size() == 3);
fakees->gc();
Expect(dummy->eslist.size() == 1);
Expect(dummy->eslist.front()->fmt.i_codec == VLC_CODEC_MPGV);
Expect(dummy->eslist.front()->b_selected == true);
/* on restart / new segment, ES MUST be reused */
fakees->recycleAll();
Expect(dummy->eslist.size() == 1);
es_format_Clean(&fmt);
es_format_Init(&fmt, VIDEO_ES, VLC_CODEC_MPGV);
/* check ID signaling so we don't blame FakeEsOut */
{
FakeESOutID fakeid(fakees, &fmt);
Expect(fakeid.isCompatible(&fakeid));
}
id = es_out_Add(out, &fmt);
fakees->commandsQueue()->Commit();
fakees->commandsQueue()->Process(drainTimes);
Expect(dummy->eslist.size() == 1);
fakees->gc();
Expect(dummy->eslist.size() == 1);
Expect(dummy->eslist.front()->b_selected == true);
/* on restart / new segment, different codec, ES MUST NOT be reused */
fakees->recycleAll();
Expect(dummy->eslist.size() == 1);
es_format_Clean(&fmt);
es_format_Init(&fmt, VIDEO_ES, VLC_CODEC_H264);
id = es_out_Add(out, &fmt);
es_format_Clean(&fmt);
es_format_Init(&fmt, AUDIO_ES, VLC_CODEC_MP4A);
id = es_out_Add(out, &fmt);
fakees->commandsQueue()->Commit();
fakees->commandsQueue()->Process(drainTimes);
Expect(dummy->eslist.size() == 3);
fakees->gc();
Expect(dummy->eslist.size() == 2);
for( DummyEsOut::ES *e : dummy->eslist ) /* selection state must have been kept */
if( e->fmt.i_cat == VIDEO_ES )
Expect(e->b_selected == true);
/* on restart / new segment, incompatible codec parameters, ES MUST NOT be reused */
fakees->setSrcID(SrcID::make());
fakees->recycleAll();
Expect(dummy->eslist.size() == 2);
es_format_Clean(&fmt);
es_format_Init(&fmt, VIDEO_ES, VLC_CODEC_H264);
id = es_out_Add(out, &fmt);
es_format_Clean(&fmt);
es_format_Init(&fmt, AUDIO_ES, VLC_CODEC_MP4A);
id = es_out_Add(out, &fmt);
fakees->commandsQueue()->Commit();
fakees->commandsQueue()->Process(drainTimes);
Expect(dummy->eslist.size() == 4);
fakees->gc();
Expect(dummy->eslist.size() == 2);
for( DummyEsOut::ES *e : dummy->eslist ) /* selection state must have been kept */
if( e->fmt.i_cat == VIDEO_ES )
Expect(e->b_selected == true);
/* on restart / new segment, with compatible codec parameters, ES MUST be reused */
fakees->recycleAll();
Expect(dummy->eslist.size() == 2);
es_format_Clean(&fmt);
es_format_Init(&fmt, AUDIO_ES, VLC_CODEC_MP4A);
id = es_out_Add(out, &fmt);
fakees->commandsQueue()->Commit();
fakees->commandsQueue()->Process(drainTimes);
Expect(dummy->eslist.size() == 2); // mp4a reused
fakees->gc(); // should drop video
Expect(dummy->eslist.size() == 1);
Expect(dummy->eslist.front()->fmt.i_codec == fmt.i_codec);
} catch (...) {
return 1;
}
es_format_Clean(&fmt);
return 0;
}
static int check2(es_out_t *out, DummyEsOut *, FakeESOut *fakees)
{
es_format_t fmt;
es_format_Init(&fmt, VIDEO_ES, VLC_CODEC_H264);
es_out_id_t *id = es_out_Add(out, &fmt);
try
{
vlc_tick_t mediaref = TMS(10000);
SegmentTimes segmentTimes(VLC_TICK_INVALID, mediaref, mediaref);
/* setExpectedTimestamp check starting from zero every segment (smooth streaming) */
fakees->setSegmentStartTimes(segmentTimes);
fakees->setExpectedTimestamp(TMS(60000));
PCR(TMS(0));
SEND(TMS(0));
PCR(TMS(5000));
Times first = fakees->commandsQueue()->getFirstTimes();
Expect(first.continuous == TMS(60000));
Expect(first.segment.demux == TMS(60000));
Expect(first.segment.media == mediaref);
fakees->resetTimestamps();
fakees->commandsQueue()->Abort(true);
/* setExpectedTimestamp check it does not apply when not starting from zero (smooth streaming) */
fakees->setSegmentStartTimes(segmentTimes);
fakees->setExpectedTimestamp(TMS(30000));
PCR(TMS(100000));
SEND(TMS(100000));
first = fakees->commandsQueue()->getFirstTimes();
fprintf(stderr,"first.continuous %" PRId64 "\n", first.continuous);
Expect(first.continuous == TMS(100000));
fprintf(stderr,"first.segment.demux %" PRId64 "\n", first.segment.demux);
Expect(first.segment.demux == TMS(100000));
fakees->resetTimestamps();
fakees->commandsQueue()->Abort(true);
/* setAssociatedTimestamp check explicit first timestamp mapping (AAC) */
fakees->setSegmentStartTimes(segmentTimes);
fakees->setAssociatedTimestamp(TMS(60000));
PCR(TMS(100000));
SEND(TMS(100000 + 5000));
PCR(TMS(100000 + 5000));
first = fakees->commandsQueue()->getFirstTimes();
fprintf(stderr,"first %" PRId64 "\n", first.continuous);
Expect(first.continuous == TMS(60000));
fakees->resetTimestamps();
fakees->commandsQueue()->Abort(true);
/* setAssociatedTimestamp check explicit MPEGTS timestamp mapping (WebVTT) */
fakees->setSegmentStartTimes(segmentTimes);
fakees->setAssociatedTimestamp(TMS(60000), TMS(500000));
SEND(TMS(500000));
PCR(TMS(500000));
first = fakees->commandsQueue()->getFirstTimes();
fprintf(stderr,"first %" PRId64 "\n", first.continuous);
Expect(first.continuous == TMS(60000));
/* setAssociatedTimestamp check explicit rolled MPEGTS timestamp mapping (WebVTT) */
fakees->setSegmentStartTimes(segmentTimes);
fakees->setAssociatedTimestamp(TMS(60000), TMS(500000) + FROM_MPEGTS(0x1FFFFFFFF));
SEND(TMS(500000));
PCR(TMS(500000));
first = fakees->commandsQueue()->getFirstTimes();
fprintf(stderr,"first %" PRId64 "\n", first.continuous);
Expect(first.continuous == TMS(60000));
} catch (...) {
return 1;
}
return 0;
}
static int check1(es_out_t *out, DummyEsOut *ctx, FakeESOut *fakees)
{
es_format_t fmt;
es_format_Init(&fmt, VIDEO_ES, VLC_CODEC_H264);
es_out_id_t *id = es_out_Add(out, &fmt);
/* ensure ES is created */
const Times drainTimes(SegmentTimes(),std::numeric_limits<vlc_tick_t>::max());
fakees->commandsQueue()->Commit();
fakees->commandsQueue()->Process(drainTimes);
try
{
vlc_tick_t mediaref = TMS(10000);
SegmentTimes segmentTimes(VLC_TICK_INVALID, mediaref, mediaref);
fakees->setSegmentStartTimes(segmentTimes);
PCR(TMS(0));
for(int i=0; i<=5000; i += 1000)
SEND(TMS(i));
PCR(TMS(5000));
Times first = fakees->commandsQueue()->getFirstTimes();
Expect(first.continuous != VLC_TICK_INVALID);
Expect(first.continuous == TMS(0));
Expect(first.segment.media == mediaref);
Expect(mediaref + DMS(5000) == fakees->commandsQueue()->getBufferingLevel().segment.media);
/* Reference has local timestamp < rolled ts */
vlc_tick_t reference = TMS(0);
fakees->resetTimestamps();
fakees->setSegmentStartTimes(segmentTimes);
fakees->setSynchronizationReference(SynchronizationReference());
SEND(reference);
PCR(reference);
Expect(fakees->commandsQueue()->getBufferingLevel().continuous == reference);
vlc_tick_t ts = TMS(1000) + FROM_MPEGTS(0x1FFFFFFFF);
ts = fakees->applyTimestampContinuity(ts);
fprintf(stderr, "timestamp %" PRId64 "\n", ts);
Expect(ts == reference + DMS(1000));
/* Reference has local multiple rolled timestamp < multiple rolled ts */
reference = VLC_TICK_0 + FROM_MPEGTS(0x1FFFFFFFF) * 2;
fakees->resetTimestamps();
fakees->setSegmentStartTimes(segmentTimes);
fakees->setSynchronizationReference(SynchronizationReference());
SEND(reference);
PCR(reference);
ts = TMS(1000) + FROM_MPEGTS(0x1FFFFFFFF) * 5;
ts = fakees->applyTimestampContinuity(ts);
fprintf(stderr, "timestamp %" PRId64 "\n", ts);
Expect(ts == reference + DMS(1000));
/* Reference has local timestamp rolled > ts */
reference = VLC_TICK_0 + FROM_MPEGTS(0x1FFFFFFFF);
fakees->resetTimestamps();
fakees->setSegmentStartTimes(segmentTimes);
fakees->setSynchronizationReference(SynchronizationReference());
SEND(reference);
PCR(reference);
Expect(fakees->commandsQueue()->getBufferingLevel().continuous == reference);
ts = VLC_TICK_0 + 1;
ts = fakees->applyTimestampContinuity(ts);
fprintf(stderr, "timestamp %" PRId64 "\n", ts);
Expect(ts == reference + 1);
/* Reference has local timestamp mutiple rolled > multiple rolled ts */
reference = VLC_TICK_0 + FROM_MPEGTS(0x1FFFFFFFF) * 5;
fakees->resetTimestamps();
fakees->setSegmentStartTimes(segmentTimes);
fakees->setSynchronizationReference(SynchronizationReference());
SEND(reference);
PCR(reference);
Expect(fakees->commandsQueue()->getBufferingLevel().continuous == reference);
ts = VLC_TICK_0 + 1 + FROM_MPEGTS(0x1FFFFFFFF) * 2;
ts = fakees->applyTimestampContinuity(ts);
fprintf(stderr, "timestamp %" PRId64 "\n", ts);
Expect(ts == reference + 1);
/* Do not trigger unwanted roll on long playbacks due to
* initial reference value */
reference = VLC_TICK_0 + FROM_MPEGTS(0x00000FFFF);
fakees->resetTimestamps();
fakees->setSegmentStartTimes(segmentTimes);
fakees->setSynchronizationReference(SynchronizationReference());
SEND(reference);
PCR(reference);
fakees->commandsQueue()->Process(drainTimes);
Expect(fakees->commandsQueue()->getBufferingLevel().continuous == reference);
Expect(fakees->hasSynchronizationReference());
Expect(ctx->dts == reference);
ts = reference + FROM_MPEGTS(0x000FFFFFF);
SEND(ts);
ts = reference + FROM_MPEGTS(0x00FFFFFFF);
SEND(ts);
ts = reference + FROM_MPEGTS(0x0FFFFFFFF);
SEND(ts);
ts = reference + FROM_MPEGTS(0x1FF000000); /* elapse enough time from ref */
SEND(ts);
PCR(ts);
fakees->commandsQueue()->Process(drainTimes);
ts = VLC_TICK_0 + 100; /* next ts has rolled */
SEND(ts);
PCR(ts);
fakees->commandsQueue()->Process(drainTimes);
Expect(ctx->dts == ts + FROM_MPEGTS(0x1FFFFFFFF));
SEND(ts + FROM_MPEGTS(0x1FFFFFFFF) * 2); /* next ts has rolled */
PCR(ts + FROM_MPEGTS(0x1FFFFFFFF) * 2);
fakees->commandsQueue()->Process(drainTimes);
Expect(ctx->dts == ts + FROM_MPEGTS(0x1FFFFFFFF));
} catch (...) {
return 1;
}
return 0;
}
static int check0(es_out_t *out, DummyEsOut *, FakeESOut *fakees)
{
es_format_t fmt;
es_format_Init(&fmt, VIDEO_ES, VLC_CODEC_H264);
es_out_id_t *id = es_out_Add(out, &fmt);
try
{
vlc_tick_t mediaref = TMS(10000);
SegmentTimes segmentTimes(VLC_TICK_INVALID, mediaref, mediaref);
fakees->setSegmentStartTimes(segmentTimes);
Expect(fakees->commandsQueue()->getBufferingLevel().segment.media == VLC_TICK_INVALID);
PCR(TMS(0));
for(int i=0; i<=5000; i += 1000)
SEND(TMS(i));
PCR(TMS(5000));
Times first = fakees->commandsQueue()->getFirstTimes();
Expect(first.continuous != VLC_TICK_INVALID);
Expect(first.continuous == TMS(0));
Expect(first.segment.media == mediaref);
Expect(mediaref + DMS(5000) == fakees->commandsQueue()->getBufferingLevel().segment.media);
// SynchronizationReference r(0, first);
// fakees->createDiscontinuityResumePoint(true, 1);
// SEND(TMS(0));
// PCR(TMS(0));
// assert(fakees->commandsQueue()->getBufferingLevel().continuous == TMS(6000));
// first = fakees->commandsQueue()->getFirstTimes();
// assert(first.continuous != VLC_TICK_INVALID);
// assert(first.continuous == TMS(0));
// assert(first.segment.media == mediaref);
} catch (...) {
return 1;
}
return 0;
}
int FakeEsOut_test()
{
DummyEsOut dummyEsOut;
struct dropesout dummy = {};
dummy.dummyesout = &dummyEsOut;
dummy.esout.cbs = &dummycbs;
int(* const tests[4])(es_out_t *, DummyEsOut *, FakeESOut *)
= { check0, check1, check2, check3 };
for(size_t i=0; i<4; i++)
{
CommandsFactory *factory = new CommandsFactory();
CommandsQueue *queue = new CommandsQueue();
FakeESOut *fakees = new FakeESOut(&dummy.esout, queue, factory);
es_out_t *out = *fakees;
int ret = tests[i](out, &dummyEsOut, fakees);
delete fakees;
if (ret)
return ret;
dummyEsOut.reset();
}
return 0;
}