Files
kdenlive/tests/audiolevelstasktest.cpp
2025-05-16 12:49:01 +02:00

262 lines
9.3 KiB
C++

/*
SPDX-FileCopyrightText: 2024 Étienne André <eti.andre@gmail.com>
SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "catch.hpp"
#include "test_utils.hpp"
#include "jobs/audiolevels/audiolevelstask.h"
#include "jobs/audiolevels/generators.h"
void computePeaksTestHelper(const QVector<int16_t> &input, const QVector<int16_t> &expectedOutput, const size_t channels)
{
QVector<int16_t> output(expectedOutput.size());
REQUIRE(input.size() % channels == 0);
REQUIRE(output.size() % channels == 0);
computePeaks(input.constData(), output.data(), channels, input.size() / channels, output.size() / channels);
REQUIRE(output == expectedOutput);
}
void dummyClbk(const int progress, const QVector<int16_t> &levels)
{
REQUIRE(progress <= 100);
REQUIRE(progress >= 0);
Q_UNUSED(levels);
}
void REQUIRE_SILENCE(const QVector<int16_t> &x)
{
for (const auto val : x) {
REQUIRE(val == 0);
}
}
TEST_CASE("computePeaks single channel no-op")
{
const QVector<int16_t> input = {1, 2, 3, 4, 5};
const QVector<int16_t> expectedOutput = {1, 2, 3, 4, 5};
computePeaksTestHelper(input, expectedOutput, 1);
}
TEST_CASE("computePeaks single channel, integer ratio")
{
const QVector<int16_t> input = {1, 2, 3, 4, 5, 6};
const QVector<int16_t> expectedOutput = {2, 4, 6};
computePeaksTestHelper(input, expectedOutput, 1);
}
TEST_CASE("computePeaks single channel, non-integer ratio")
{
const QVector<int16_t> input = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
const QVector<int16_t> expectedOutput = {1, 2, 3, 5, 6, 7, 8, 10};
computePeaksTestHelper(input, expectedOutput, 1);
}
TEST_CASE("computePeaks single channel, inverse integer ratio")
{
const QVector<int16_t> input = {1, 2, 3};
const QVector<int16_t> expectedOutput = {1, 1, 1, 2, 2, 2, 3, 3, 3};
computePeaksTestHelper(input, expectedOutput, 1);
}
TEST_CASE("computePeaks single channel, inverse non-integer ratio")
{
const QVector<int16_t> input = {1, 2};
const QVector<int16_t> expectedOutput = {1, 1, 1, 1, 2, 2, 2};
computePeaksTestHelper(input, expectedOutput, 1);
}
TEST_CASE("computePeaks multi channel no-op")
{
const QVector<int16_t> input = {1, 2, 3, 4, 5, 6};
const QVector<int16_t> expectedOutput = {1, 2, 3, 4, 5, 6};
computePeaksTestHelper(input, expectedOutput, 2);
}
TEST_CASE("computePeaks multi channel, integer ratio")
{
const QVector<int16_t> input = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
const QVector<int16_t> expectedOutput = {3, 4, 7, 8, 11, 12};
computePeaksTestHelper(input, expectedOutput, 2);
}
TEST_CASE("computePeaks multi channel, non-integer ratio")
{
const QVector<int16_t> input = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
const QVector<int16_t> expectedOutput = {1, 2, 5, 6, 9, 10};
computePeaksTestHelper(input, expectedOutput, 2);
}
TEST_CASE("computePeaks multi channel, inverse integer ratio")
{
const QVector<int16_t> input = {1, 2, 3, 4, 5, 6};
const QVector<int16_t> expectedOutput = {1, 2, 1, 2, 3, 4, 3, 4, 5, 6, 5, 6};
computePeaksTestHelper(input, expectedOutput, 2);
}
TEST_CASE("computePeaks multi channel, inverse non-integer ratio")
{
const QVector<int16_t> input = {1, 2, 3, 4};
const QVector<int16_t> expectedOutput = {1, 2, 1, 2, 1, 2, 3, 4, 3, 4};
computePeaksTestHelper(input, expectedOutput, 2);
}
TEST_CASE("computePeaks large input")
{
QVector<int16_t> input;
for (int i = 0; i < 100000; ++i) {
input.push_back(i % 10);
}
const QVector<int16_t> expectedOutput(10000, 9);
computePeaksTestHelper(input, expectedOutput, 1);
}
TEST_CASE("generateLibav bad stream index")
{
const auto output = generateLibav(9999, sourcesPath + "/dataset/mono.flac", 10, 30, &dummyClbk, 0);
REQUIRE(output.isEmpty());
}
TEST_CASE("generateLibav bad file path")
{
const auto output = generateLibav(0, "i-do-not-exist.mp3", 10, 30, &dummyClbk, 0);
REQUIRE(output.isEmpty());
}
TEST_CASE("generateMLT bad file path")
{
const auto output = generateMLT(0, "avformat", "i-do-not-exist.mp3", 1, &dummyClbk, 0);
REQUIRE(output.isEmpty());
}
TEST_CASE("generateLibav correct output length")
{
const auto output = generateLibav(0, sourcesPath + "/dataset/mono.flac", 30, 30, &dummyClbk, 0);
REQUIRE(output.size() == 30 * AUDIOLEVELS_POINTS_PER_FRAME);
}
TEST_CASE("generateMLT correct output length")
{
pCore->setCurrentProfile(QStringLiteral("dv_pal"));
const auto profileFps = pCore->getCurrentFps();
const auto output = generateMLT(0, "avformat", sourcesPath + "/dataset/mono.flac", 1, &dummyClbk, 0);
REQUIRE(output.size() == profileFps * AUDIOLEVELS_POINTS_PER_FRAME);
}
TEST_CASE("generateLibav canceled")
{
const auto output = generateLibav(0, sourcesPath + "/dataset/mono.flac", 10, 30, &dummyClbk, 1);
REQUIRE(output.isEmpty());
}
TEST_CASE("generateMLT canceled")
{
const auto output = generateMLT(0, "avformat", sourcesPath + "/dataset/mono.flac", 1, &dummyClbk, 1);
REQUIRE(output.isEmpty());
}
TEST_CASE("generateLibav not enough frames requested")
{
const auto output = generateLibav(0, sourcesPath + "/dataset/mono.flac", 1, 30, &dummyClbk, 0);
REQUIRE(output.isEmpty());
}
TEST_CASE("both methods on mono audio")
{
pCore->setCurrentProfile(QStringLiteral("dv_pal"));
const auto profileFps = pCore->getCurrentFps();
const auto a = generateMLT(0, "avformat", sourcesPath + "/dataset/mono.flac", 1, &dummyClbk, 0);
const auto lengthInFrames = a.size() / 1 / AUDIOLEVELS_POINTS_PER_FRAME;
const auto b = generateLibav(0, sourcesPath + "/dataset/mono.flac", lengthInFrames, profileFps, &dummyClbk, 0);
REQUIRE(!a.isEmpty());
REQUIRE(a.size() % AUDIOLEVELS_POINTS_PER_FRAME == 0);
REQUIRE(a == b);
}
TEST_CASE("both methods on stereo audio")
{
pCore->setCurrentProfile(QStringLiteral("dv_pal"));
const auto profileFps = pCore->getCurrentFps();
const auto a = generateMLT(0, "avformat", sourcesPath + "/dataset/stereo.flac", 2, &dummyClbk, 0);
const auto lengthInFrames = a.size() / 2 / AUDIOLEVELS_POINTS_PER_FRAME;
const auto b = generateLibav(0, sourcesPath + "/dataset/stereo.flac", lengthInFrames, profileFps, &dummyClbk, 0);
REQUIRE(!a.isEmpty());
REQUIRE(a.size() % AUDIOLEVELS_POINTS_PER_FRAME == 0);
REQUIRE(a.size() % 2 == 0);
REQUIRE(a == b);
}
TEST_CASE("both methods on multiple audio streams")
{
pCore->setCurrentProfile(QStringLiteral("dv_pal"));
const auto profileFps = pCore->getCurrentFps();
SECTION("Stream 0: mono")
{
const auto a = generateMLT(0, "avformat", sourcesPath + "/dataset/lots_of_audio_streams.mkv", 1, &dummyClbk, 0);
const auto lengthInFrames = a.size() / 1 / AUDIOLEVELS_POINTS_PER_FRAME;
const auto b = generateLibav(0, sourcesPath + "/dataset/lots_of_audio_streams.mkv", lengthInFrames, profileFps, &dummyClbk, 0);
REQUIRE(!a.isEmpty());
REQUIRE(a.size() % AUDIOLEVELS_POINTS_PER_FRAME == 0);
REQUIRE(a == b);
}
SECTION("Stream 1: stereo")
{
const auto a = generateMLT(1, "avformat", sourcesPath + "/dataset/lots_of_audio_streams.mkv", 2, &dummyClbk, 0);
const auto lengthInFrames = a.size() / 2 / AUDIOLEVELS_POINTS_PER_FRAME;
const auto b = generateLibav(1, sourcesPath + "/dataset/lots_of_audio_streams.mkv", lengthInFrames, profileFps, &dummyClbk, 0);
REQUIRE(!a.isEmpty());
REQUIRE(a.size() % AUDIOLEVELS_POINTS_PER_FRAME == 0);
REQUIRE(a.size() % 2 == 0);
REQUIRE(a == b);
}
SECTION("Stream 2: surround")
{
const auto a = generateMLT(2, "avformat", sourcesPath + "/dataset/lots_of_audio_streams.mkv", 6, &dummyClbk, 0);
const auto lengthInFrames = a.size() / 6 / AUDIOLEVELS_POINTS_PER_FRAME;
const auto b = generateLibav(2, sourcesPath + "/dataset/lots_of_audio_streams.mkv", lengthInFrames, profileFps, &dummyClbk, 0);
REQUIRE(!a.isEmpty());
REQUIRE(a.size() % AUDIOLEVELS_POINTS_PER_FRAME == 0);
REQUIRE(a.size() % 6 == 0);
REQUIRE(a == b);
}
}
TEST_CASE("(de)serialize audio levels")
{
const auto input = QVector<int16_t>{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto tmp = QTemporaryFile();
REQUIRE(tmp.open());
AudioLevelsTask::saveLevelsToCache(tmp.fileName(), input);
const auto deserialized = AudioLevelsTask::getLevelsFromCache(tmp.fileName());
REQUIRE(deserialized == input);
}
TEST_CASE("MLT noise generator")
{
auto xml = QTemporaryFile();
REQUIRE(xml.open());
QTextStream out(&xml);
out << R"(<?xml version="1.0" encoding="utf-8"?>
<mlt LC_NUMERIC="C" version="7.28.0" root="/home/etiandre/git/kdenlive/build/bin">
<profile description="HD 1080p 30 fps" width="1920" height="1080" progressive="1" sample_aspect_num="1" sample_aspect_den="1" display_aspect_num="16" display_aspect_den="9" frame_rate_num="30" frame_rate_den="1" colorspace="709"/>
<producer id="producer0" in="0" out="59">
<property name="length">60</property>
<property name="eof">pause</property>
<property name="resource">&lt;producer&gt;</property>
<property name="aspect_ratio">1</property>
<property name="mlt_service">noise</property>
</producer>
<tractor id="tractor0" in="0" out="59">
<track producer="producer0"/>
</tractor>
</mlt>
)";
xml.close();
const auto output = generateMLT(1, "xml", xml.fileName(), 2, dummyClbk, 0);
REQUIRE(output.size() == 60 * 2 * AUDIOLEVELS_POINTS_PER_FRAME);
}