Merge pull request #177 from daleglass-overte/audio-testing

Create audio tests for AudioClient and codecs
This commit is contained in:
Dale Glass 2022-11-19 21:55:12 +01:00 committed by GitHub
commit 7b60552f78
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 272 additions and 1 deletions

View file

@ -1,7 +1,7 @@
# Declare dependencies
macro (SETUP_TESTCASE_DEPENDENCIES)
# link in the shared libraries
link_hifi_libraries(shared audio networking)
link_hifi_libraries(shared audio audio-client plugins networking)
package_libraries_for_deployment()
endmacro ()

View file

@ -0,0 +1,87 @@
#include <QSignalSpy>
#include <QDebug>
#include "AudioTests.h"
#include "AudioClient.h"
#include "DependencyManager.h"
#include "NodeList.h"
#include "plugins/CodecPlugin.h"
#include "plugins/PluginManager.h"
QTEST_MAIN(AudioTests)
Q_DECLARE_METATYPE(QList<HifiAudioDeviceInfo>)
void AudioTests::initTestCase() {
// AudioClient starts networking, but for the purposes of the tests here we don't care,
// so just got to use some port.
int listenPort = 10000;
DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
DependencyManager::set<NodeList>(NodeType::Agent, listenPort);
DependencyManager::set<AudioClient>();
DependencyManager::set<PluginManager>();
QSharedPointer<AudioClient> ac = DependencyManager::get<AudioClient>();
qRegisterMetaType<QList<HifiAudioDeviceInfo>>();
ac->startThread();
}
void AudioTests::listAudioDevices() {
QSharedPointer<AudioClient> ac = DependencyManager::get<AudioClient>();
QVERIFY(!ac.isNull());
/*
// AudioClient::devicesChanged is declared as:
// void devicesChanged(QAudio::Mode mode, const QList<HifiAudioDeviceInfo>& devices);
//
// Unfortunately with QSignalSpy we have to use the old SIGNAL() syntax, so it was a bit tricky
// to figure out how to get the signal to connect. The snippet below lists signals in the format
// that Qt understands. Turns out we lose the 'const &'.
const QMetaObject *mo = ac->metaObject();
QList<QString> signalSignatures;
// Start from MyClass members
for(int methodIdx = mo->methodOffset(); methodIdx < mo->methodCount(); ++methodIdx) {
QMetaMethod mmTest = mo->method(methodIdx);
switch((int)mmTest.methodType()) {
case QMetaMethod::Signal:
signalSignatures.append(QString(mmTest.methodSignature()));
qDebug() << "SIG: " << QString(mmTest.methodSignature());
break;
}
}
*/
QSignalSpy spy(ac.get(), SIGNAL(devicesChanged(QAudio::Mode,QList<HifiAudioDeviceInfo>)));
QVERIFY(spy.isValid()); // This checks that the signal has connected
spy.wait(15000);
// We always get two events here, one for audio input, and one for output,
// but signals keep coming and we could potentially get more repetitions.
QVERIFY(spy.count() > 0);
qDebug() << "Received" << spy.count() << "device events";
// QSignalSpy is a QList, which stores the received signals. We can then examine it to see
// what we got.
for(auto event : spy) {
QAudio::Mode mode = qvariant_cast<QAudio::Mode>(event.at(0));
QList<HifiAudioDeviceInfo> devs = qvariant_cast<QList<HifiAudioDeviceInfo>>(event.at(1));
QVERIFY(devs.count() > 0);
qDebug() << "Mode:" << mode;
for(auto dev : devs) {
qDebug() << "\t" << dev.deviceName();
}
}
}

View file

@ -0,0 +1,27 @@
//
// AudioTests.h
// tests/audio/src
//
// Created by Dale Glass on 27/8/2022
// Copyright 2022 Overte e.V.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_AudioTests_h
#define hifi_AudioTests_h
#include <QtTest/QtTest>
class AudioTests : public QObject {
Q_OBJECT
private slots:
void initTestCase();
void listAudioDevices();
};
#endif // hifi_AudioTests_h

View file

@ -0,0 +1,125 @@
#include <QSignalSpy>
#include <QDebug>
#include <QCoreApplication>
#include <QFile>
#include "CodecTests.h"
#include "AudioClient.h"
#include "DependencyManager.h"
#include "NodeList.h"
#include "plugins/CodecPlugin.h"
#include "plugins/PluginManager.h"
QTEST_MAIN(CodecTests)
void CodecTests::initTestCase() {
DependencyManager::set<PluginManager>();
QDir testPath (QCoreApplication::applicationDirPath());
QDir interfacePluginPath = testPath;
qDebug() << "Our directory is" << testPath;
interfacePluginPath.cdUp();
interfacePluginPath.cdUp();
interfacePluginPath.cd("interface");
interfacePluginPath.cd("plugins");
interfacePluginPath.makeAbsolute();
QString ourPluginPath = testPath.filePath("plugins");
qDebug() << "Interface plugins are at" << interfacePluginPath;
qDebug() << "Our plugins are at" << ourPluginPath;
QFile::link(interfacePluginPath.path(), ourPluginPath);
}
void CodecTests::loadCodecs() {
const auto& codecPlugins = PluginManager::getInstance()->getCodecPlugins();
QVERIFY(codecPlugins.size() > 0);
for (const auto& plugin : codecPlugins) {
auto codecName = plugin->getName();
qDebug() << "Codec:" << codecName << ", supported=" << plugin->isSupported();
}
}
void CodecTests::testEncoders() {
const auto& codecPlugins = PluginManager::getInstance()->getCodecPlugins();
QVERIFY(codecPlugins.size() > 0);
for (const auto& plugin : codecPlugins) {
if (!plugin->isSupported()) {
qWarning() << "Skipping unsupported plugin" << plugin->getName();
continue;
}
Encoder* encoder = plugin->createEncoder(AudioConstants::SAMPLE_RATE, AudioConstants::STEREO);
QVERIFY(encoder != nullptr);
QByteArray data(AudioConstants::NETWORK_FRAME_BYTES_STEREO, 0);
QByteArray encoded;
encoder->encode(data, encoded);
QVERIFY(encoded.size() > 0);
qDebug() << "Codec" << plugin->getName() << "encoded empty buffer of" << data.size() << "bytes into" << encoded.size();
}
}
void CodecTests::testDecoders () {
const auto& codecPlugins = PluginManager::getInstance()->getCodecPlugins();
QVERIFY(codecPlugins.size() > 0);
for (const auto& plugin : codecPlugins) {
if (!plugin->isSupported()) {
qWarning() << "Skipping unsupported plugin" << plugin->getName();
continue;
}
Encoder* encoder = plugin->createEncoder(AudioConstants::SAMPLE_RATE, AudioConstants::STEREO);
Decoder* decoder = plugin->createDecoder(AudioConstants::SAMPLE_RATE, AudioConstants::STEREO);
QVERIFY(encoder != nullptr);
QByteArray data(AudioConstants::NETWORK_FRAME_BYTES_STEREO, 0);
QByteArray encoded;
QByteArray decoded;
QByteArray lost(AudioConstants::NETWORK_FRAME_BYTES_STEREO, 0);
encoder->encode(data, encoded);
decoder->decode(encoded, decoded);
QVERIFY(encoded.size() > 0);
QVERIFY(decoded.size() > 0);
QVERIFY(decoded.size() == data.size());
QVERIFY(lost.size() > 0);
qDebug() << "Codec" << plugin->getName() << "encoded empty buffer of" << data.size() << "bytes into" << encoded.size() << "and decoded back into" << decoded.size();
// This is here mostly for valgrind testing -- we can't really validate anything, but we can see if it crashes.
decoder->lostFrame(lost);
QVERIFY(lost.size() > 0);
qDebug() << "Codec" << plugin->getName() << "decoded a lost frame";
}
}

View file

@ -0,0 +1,32 @@
//
// AudioTests.h
// tests/audio/src
//
// Created by Dale Glass on 27/8/2022
// Copyright 2022 Overte e.V.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_AudioTests_h
#define hifi_AudioTests_h
#include <QtTest/QtTest>
class CodecTests : public QObject {
Q_OBJECT
private slots:
void initTestCase();
void loadCodecs();
void testEncoders();
void testDecoders();
};
#endif // hifi_AudioTests_h