Skip to content

Commit daa4c00

Browse files
committed
Some opus tests
1 parent f907722 commit daa4c00

File tree

5 files changed

+353
-77
lines changed

5 files changed

+353
-77
lines changed

tests/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ set (target_modules
6262
yup_events
6363
yup_data_model
6464
yup_graphics
65-
pffft_library)
65+
pffft_library
66+
opus_library)
6667

6768
if (NOT YUP_PLATFORM_EMSCRIPTEN)
6869
list (APPEND target_modules yup_gui yup_audio_gui)

tests/yup_audio_formats.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
11
#include "yup_audio_formats/yup_AudioFormatManager.cpp"
2-
#include "yup_audio_formats/yup_WaveAudioFormat.cpp"
2+
#include "yup_audio_formats/yup_WaveAudioFormat.cpp"
3+
4+
#if YUP_MODULE_AVAILABLE_opus_library
5+
#include "yup_audio_formats/yup_OpusAudioFormat.cpp"
6+
#endif
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
==============================================================================
3+
4+
This file is part of the YUP library.
5+
Copyright (c) 2025 - kunitoki@gmail.com
6+
7+
YUP is an open source library subject to open-source licensing.
8+
9+
The code included in this file is provided under the terms of the ISC license
10+
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
11+
to use, copy, modify, and/or distribute this software for any purpose with or
12+
without fee is hereby granted provided that the above copyright notice and
13+
this permission notice appear in all copies.
14+
15+
YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
16+
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
17+
DISCLAIMED.
18+
19+
==============================================================================
20+
*/
21+
22+
#pragma once
23+
24+
#include <yup_audio_formats/yup_audio_formats.h>
25+
26+
using namespace yup;
27+
28+
struct AudioValidationResult
29+
{
30+
bool hasClippedSamples = false;
31+
bool hasExtremeValues = false;
32+
float maxAbsValue = 0.0f;
33+
float minValue = 0.0f;
34+
float maxValue = 0.0f;
35+
int clippedSampleCount = 0;
36+
int extremeValueCount = 0;
37+
};
38+
39+
inline AudioValidationResult validateAudioData (AudioFormatReader& reader)
40+
{
41+
AudioValidationResult result;
42+
43+
if (reader.lengthInSamples <= 0)
44+
return result;
45+
46+
const int bufferSize = 4096;
47+
AudioBuffer<float> buffer (static_cast<int> (reader.numChannels), bufferSize);
48+
49+
int64 samplesRemaining = reader.lengthInSamples;
50+
int64 currentPos = 0;
51+
52+
while (samplesRemaining > 0)
53+
{
54+
const int samplesToRead = static_cast<int> (std::min ((int64) bufferSize, samplesRemaining));
55+
56+
if (! reader.read (&buffer, 0, samplesToRead, currentPos, true, true))
57+
break;
58+
59+
for (int ch = 0; ch < buffer.getNumChannels(); ++ch)
60+
{
61+
const float* channelData = buffer.getReadPointer (ch);
62+
63+
for (int sample = 0; sample < samplesToRead; ++sample)
64+
{
65+
const float value = channelData[sample];
66+
const float absValue = std::abs (value);
67+
68+
result.minValue = std::min (result.minValue, value);
69+
result.maxValue = std::max (result.maxValue, value);
70+
result.maxAbsValue = std::max (result.maxAbsValue, absValue);
71+
72+
const float clipThreshold = 1.0001f;
73+
if (absValue > clipThreshold)
74+
{
75+
result.hasClippedSamples = true;
76+
result.clippedSampleCount++;
77+
}
78+
79+
const float extremeThreshold = 10.0f;
80+
if (absValue > extremeThreshold)
81+
{
82+
result.hasExtremeValues = true;
83+
result.extremeValueCount++;
84+
}
85+
}
86+
}
87+
88+
currentPos += samplesToRead;
89+
samplesRemaining -= samplesToRead;
90+
}
91+
92+
return result;
93+
}
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
/*
2+
==============================================================================
3+
4+
This file is part of the YUP library.
5+
Copyright (c) 2025 - kunitoki@gmail.com
6+
7+
YUP is an open source library subject to open-source licensing.
8+
9+
The code included in this file is provided under the terms of the ISC license
10+
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
11+
to use, copy, modify, and/or distribute this software for any purpose with or
12+
without fee is hereby granted provided that the above copyright notice and
13+
this permission notice appear in all copies.
14+
15+
YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
16+
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
17+
DISCLAIMED.
18+
19+
==============================================================================
20+
*/
21+
22+
#include <yup_audio_formats/yup_audio_formats.h>
23+
24+
#include "yup_AudioFormatTools.h"
25+
26+
#include <gtest/gtest.h>
27+
28+
using namespace yup;
29+
30+
namespace
31+
{
32+
const std::vector<String> getAllOpusTestFiles()
33+
{
34+
return {
35+
"M1F1-float32-vbr.opus",
36+
"M1F1-float32.opus",
37+
"M1F1-int16-vbr.opus",
38+
"M1F1-int16.opus",
39+
"M1F1-int24-vbr.opus",
40+
"M1F1-int24.opus",
41+
"M1F1-uint8-vbr.opus",
42+
"M1F1-uint8.opus"
43+
};
44+
}
45+
} // namespace
46+
47+
class OpusAudioFormatTests : public ::testing::Test
48+
{
49+
protected:
50+
void SetUp() override
51+
{
52+
format = std::make_unique<OpusAudioFormat>();
53+
}
54+
55+
std::unique_ptr<OpusAudioFormat> format;
56+
};
57+
58+
TEST_F (OpusAudioFormatTests, GetFormatNameReturnsOpus)
59+
{
60+
const String& name = format->getFormatName();
61+
EXPECT_FALSE (name.isEmpty());
62+
EXPECT_TRUE (name.containsIgnoreCase ("opus"));
63+
}
64+
65+
TEST_F (OpusAudioFormatTests, GetFileExtensionsIncludesOpus)
66+
{
67+
Array<String> extensions = format->getFileExtensions();
68+
EXPECT_FALSE (extensions.isEmpty());
69+
70+
bool foundOpus = false;
71+
for (const auto& ext : extensions)
72+
{
73+
if (ext.equalsIgnoreCase (".opus") || ext.equalsIgnoreCase ("opus"))
74+
{
75+
foundOpus = true;
76+
break;
77+
}
78+
}
79+
EXPECT_TRUE (foundOpus);
80+
}
81+
82+
TEST_F (OpusAudioFormatTests, GetPossibleBitDepthsAndSampleRates)
83+
{
84+
Array<int> bitDepths = format->getPossibleBitDepths();
85+
Array<int> sampleRates = format->getPossibleSampleRates();
86+
87+
EXPECT_FALSE (bitDepths.isEmpty());
88+
EXPECT_FALSE (sampleRates.isEmpty());
89+
EXPECT_TRUE (bitDepths.contains (32));
90+
EXPECT_TRUE (sampleRates.contains (48000));
91+
}
92+
93+
TEST_F (OpusAudioFormatTests, CanDoMonoAndStereo)
94+
{
95+
EXPECT_TRUE (format->canDoMono());
96+
EXPECT_TRUE (format->canDoStereo());
97+
}
98+
99+
TEST_F (OpusAudioFormatTests, IsCompressed)
100+
{
101+
EXPECT_TRUE (format->isCompressed());
102+
}
103+
104+
TEST_F (OpusAudioFormatTests, CreateReaderForNullStream)
105+
{
106+
auto reader = format->createReaderFor (nullptr);
107+
EXPECT_EQ (nullptr, reader);
108+
}
109+
110+
TEST_F (OpusAudioFormatTests, CreateWriterForNullStream)
111+
{
112+
auto writer = format->createWriterFor (nullptr, 48000, 2, 32, {}, 0);
113+
EXPECT_EQ (nullptr, writer);
114+
}
115+
116+
#if ! YUP_EMSCRIPTEN
117+
class OpusAudioFormatFileTests : public ::testing::Test
118+
{
119+
protected:
120+
void SetUp() override
121+
{
122+
format = std::make_unique<OpusAudioFormat>();
123+
testDataDir = File (__FILE__)
124+
.getParentDirectory()
125+
.getParentDirectory()
126+
.getChildFile ("data")
127+
.getChildFile ("sounds");
128+
}
129+
130+
std::unique_ptr<OpusAudioFormat> format;
131+
File testDataDir;
132+
};
133+
134+
TEST_F (OpusAudioFormatFileTests, TestAllOpusFilesCanBeOpened)
135+
{
136+
auto opusFiles = getAllOpusTestFiles();
137+
138+
for (const auto& filename : opusFiles)
139+
{
140+
File opusFile = testDataDir.getChildFile (filename);
141+
142+
if (! opusFile.exists())
143+
{
144+
FAIL() << "Test file does not exist: " << filename.toRawUTF8();
145+
continue;
146+
}
147+
148+
std::unique_ptr<FileInputStream> inputStream = std::make_unique<FileInputStream> (opusFile);
149+
if (! inputStream->openedOk())
150+
{
151+
FAIL() << "Could not open file stream for: " << filename.toRawUTF8();
152+
continue;
153+
}
154+
155+
auto reader = format->createReaderFor (inputStream.get());
156+
if (reader == nullptr)
157+
{
158+
inputStream.release();
159+
FAIL() << "Could not create reader for: " << filename.toRawUTF8();
160+
continue;
161+
}
162+
163+
EXPECT_EQ (48000.0, reader->sampleRate) << "Unexpected sample rate for: " << filename.toRawUTF8();
164+
EXPECT_GT (reader->numChannels, 0) << "Invalid channel count for: " << filename.toRawUTF8();
165+
EXPECT_GE (reader->lengthInSamples, 0) << "Invalid length for: " << filename.toRawUTF8();
166+
EXPECT_EQ (32, reader->bitsPerSample) << "Unexpected bit depth for: " << filename.toRawUTF8();
167+
EXPECT_TRUE (reader->usesFloatingPointData) << "Expected float data for: " << filename.toRawUTF8();
168+
169+
if (reader->lengthInSamples > 0)
170+
{
171+
const int samplesToRead = static_cast<int> (std::min (reader->lengthInSamples, static_cast<int64> (1024)));
172+
AudioBuffer<float> buffer (static_cast<int> (reader->numChannels), samplesToRead);
173+
174+
bool readSuccess = reader->read (&buffer, 0, samplesToRead, 0, true, true);
175+
EXPECT_TRUE (readSuccess) << "Failed to read samples from: " << filename.toRawUTF8();
176+
}
177+
178+
inputStream.release();
179+
}
180+
}
181+
182+
TEST_F (OpusAudioFormatFileTests, TestOpusFilesHaveValidData)
183+
{
184+
auto opusFiles = getAllOpusTestFiles();
185+
186+
for (const auto& filename : opusFiles)
187+
{
188+
File opusFile = testDataDir.getChildFile (filename);
189+
190+
if (! opusFile.exists())
191+
{
192+
FAIL() << "Test file does not exist: " << filename.toRawUTF8();
193+
continue;
194+
}
195+
196+
std::unique_ptr<FileInputStream> inputStream = std::make_unique<FileInputStream> (opusFile);
197+
if (! inputStream->openedOk())
198+
{
199+
FAIL() << "Could not open file stream for: " << filename.toRawUTF8();
200+
continue;
201+
}
202+
203+
auto reader = format->createReaderFor (inputStream.get());
204+
if (reader == nullptr)
205+
{
206+
inputStream.release();
207+
FAIL() << "Could not create reader for: " << filename.toRawUTF8();
208+
continue;
209+
}
210+
211+
auto validationResult = validateAudioData (*reader);
212+
213+
EXPECT_FALSE (validationResult.hasClippedSamples)
214+
<< "File " << filename.toRawUTF8() << " contains "
215+
<< validationResult.clippedSampleCount << " samples exceeding ±1.0 (peak: "
216+
<< validationResult.maxAbsValue << ")";
217+
218+
EXPECT_FALSE (validationResult.hasExtremeValues)
219+
<< "File " << filename.toRawUTF8() << " contains "
220+
<< validationResult.extremeValueCount << " extreme values (peak: "
221+
<< validationResult.maxAbsValue << ")";
222+
223+
EXPECT_LE (validationResult.maxAbsValue, 1.5f)
224+
<< "File " << filename.toRawUTF8() << " has maximum absolute value of "
225+
<< validationResult.maxAbsValue << " which seems unusually high";
226+
227+
EXPECT_GE (validationResult.minValue, -1.5f)
228+
<< "File " << filename.toRawUTF8() << " has minimum value of "
229+
<< validationResult.minValue << " which seems unusually low";
230+
231+
EXPECT_LE (validationResult.maxValue, 1.5f)
232+
<< "File " << filename.toRawUTF8() << " has maximum value of "
233+
<< validationResult.maxValue << " which seems unusually high";
234+
235+
inputStream.release();
236+
}
237+
}
238+
239+
TEST_F (OpusAudioFormatFileTests, CreateWriterReturnsNull)
240+
{
241+
File tempFile = File::createTempFile (".opus");
242+
auto deleteTempFileAtExit = ScopeGuard { [&]
243+
{
244+
tempFile.deleteFile();
245+
} };
246+
247+
std::unique_ptr<FileOutputStream> outputStream = std::make_unique<FileOutputStream> (tempFile);
248+
auto writer = format->createWriterFor (outputStream.get(), 48000, 2, 32, {}, 0);
249+
EXPECT_EQ (nullptr, writer);
250+
}
251+
#endif

0 commit comments

Comments
 (0)