/*
* This file is part of the ZoneMinder Project. See AUTHORS file for Copyright information
*
* 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.
*
* 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 .
*/
#include "zm_catch2.h"
#include "zm_monitor.h"
#include
// Monitor::KeepDtsContinuous(): a camera whose clock steps back (refs #4242).
// Once the step is taken out at capture, every packet after it continues from
// where the stream was, for video and audio alike.
namespace {
void EnsureConfig() {
if (!config.font_file_location) config.font_file_location = "";
if (!config.event_close_mode) config.event_close_mode = "idle";
}
// Monitor keeps the state; this exposes the method and the offset to the tests.
class DtsContinuity : public Monitor {
public:
DtsContinuity() : Monitor() {}
int64_t Apply(AVPacket *packet, AVRational time_base) { return KeepDtsContinuous(packet, time_base); }
Microseconds Offset() const { return Microseconds(dts_offset_); }
};
constexpr AVRational kVideoTb = {1, 90000}; // RTP/MPEG-TS video
constexpr AVRational kAudioTb = {1, 8000};
constexpr int kVideo = 0;
constexpr int kAudio = 1;
struct Pkt {
av_packet_ptr p{av_packet_alloc()};
Pkt(int stream, int64_t dts, int64_t duration) {
p->stream_index = stream;
p->dts = dts;
p->pts = dts;
p->duration = duration;
}
};
// Feed dts values (in the stream's time base) through and return what comes out.
std::vector Run(DtsContinuity &c, int stream, AVRational tb, const std::vector &dts,
int64_t duration) {
std::vector out;
for (int64_t d : dts) {
Pkt pkt(stream, d, duration);
c.Apply(pkt.p.get(), tb);
REQUIRE(pkt.p->pts == pkt.p->dts);
out.push_back(pkt.p->dts);
}
return out;
}
} // namespace
TEST_CASE("Monitor dts continuity: a continuous stream is left alone", "[Monitor][dts]") {
EnsureConfig();
DtsContinuity c;
std::vector in = {0, 3000, 6000, 9000, 12000};
CHECK(Run(c, kVideo, kVideoTb, in, 3000) == in);
CHECK(c.Offset() == Microseconds(0));
}
TEST_CASE("Monitor dts continuity: jitter under the threshold is left alone", "[Monitor][dts]") {
EnsureConfig();
DtsContinuity c;
// 100 ms back is reordering jitter: the video store's reorder queue sorts it.
std::vector in = {0, 3000, 6000, 6000 - 9000, 9000};
CHECK(Run(c, kVideo, kVideoTb, in, 3000) == in);
CHECK(c.Offset() == Microseconds(0));
}
TEST_CASE("Monitor dts continuity: a step back continues from where the stream was", "[Monitor][dts]") {
EnsureConfig();
DtsContinuity c;
// 8.933 s back (803970 ticks at 90 kHz), the step in the #4242 reproduction.
std::vector out = Run(c, kVideo, kVideoTb, {900000, 903000, 906000, 906000 - 803970, 906000 - 803970 + 3000}, 3000);
CHECK(out == std::vector({900000, 903000, 906000, 909000, 912000}));
CHECK(c.Offset() > Microseconds(8900000));
}
TEST_CASE("Monitor dts continuity: the 1.048 s step from the report is taken out", "[Monitor][dts]") {
EnsureConfig();
DtsContinuity c;
std::vector out = Run(c, kVideo, kVideoTb, {200000, 203000, 203000 - 94320, 203000 - 94320 + 3000}, 3000);
CHECK(out == std::vector({200000, 203000, 206000, 209000}));
}
TEST_CASE("Monitor dts continuity: audio follows the offset video set", "[Monitor][dts]") {
EnsureConfig();
DtsContinuity c;
// Both clocks step back 8 s together. Video sees it first and moves the
// shared offset; audio, stepped back by the same amount, carries on.
Run(c, kVideo, kVideoTb, {900000, 903000}, 3000);
Run(c, kAudio, kAudioTb, {80000, 80160}, 160);
std::vector video = Run(c, kVideo, kVideoTb, {903000 - 720000 + 3000}, 3000);
CHECK(video == std::vector({906000}));
std::vector audio = Run(c, kAudio, kAudioTb, {80320 - 64000, 80480 - 64000}, 160);
// Continuous and increasing, within a video frame of where it would have been.
CHECK(audio[1] - audio[0] == 160);
CHECK(audio[0] > 80160);
CHECK(audio[0] < 80320 + 8000 / 30);
}
TEST_CASE("Monitor dts continuity: without a duration the last step stands in", "[Monitor][dts]") {
EnsureConfig();
DtsContinuity c;
std::vector out = Run(c, kVideo, kVideoTb, {900000, 903600, 907200, 107200}, 0);
CHECK(out == std::vector({900000, 903600, 907200, 910800}));
}
TEST_CASE("Monitor dts continuity: packets without a dts pass through", "[Monitor][dts]") {
EnsureConfig();
DtsContinuity c;
Pkt pkt(kVideo, AV_NOPTS_VALUE, 3000);
pkt.p->pts = AV_NOPTS_VALUE;
CHECK(c.Apply(pkt.p.get(), kVideoTb) == 0);
CHECK(pkt.p->dts == AV_NOPTS_VALUE);
CHECK(pkt.p->pts == AV_NOPTS_VALUE);
}