ZaStoGram/TMessagesProj/jni/voip/tgcalls/group/StreamingAudioRendererTest.cpp
2026-09-24 16:17:30 +04:00

125 lines
4.4 KiB
C++

#include "group/StreamingAudioRenderer.h"
#include <cstdio>
#include <cstdlib>
#include <vector>
namespace {
int g_failures = 0;
#define CHECK_TRUE(cond) \
do { \
if (!(cond)) { \
std::printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \
g_failures++; \
} \
} while (0)
// A stream source whose every sample is `value`, regardless of the format asked for.
tgcalls::StreamingAudioRenderer::Source constantSource(int16_t value) {
return [value](int16_t *samples, size_t numSamples, size_t numChannels, uint32_t) {
for (size_t i = 0; i < numSamples * numChannels; i++) {
samples[i] = value;
}
};
}
// Number of samples in [from, buffer.size()) that differ from `expected` by more than `tolerance`.
size_t countOutside(std::vector<int16_t> const &buffer, size_t from, int16_t expected, int tolerance) {
size_t count = 0;
for (size_t i = from; i < buffer.size(); i++) {
if (std::abs((int)buffer[i] - (int)expected) > tolerance) {
count++;
}
}
return count;
}
// Most third-party A2DP devices negotiate a 44.1 kHz playout rate. 48000 -> 44100 reduces
// to 160:147, which the legacy webrtc::Resampler does not support, and the live stream was
// silent on those devices while AirPods (48 kHz) played fine.
void TestRendersInto44100HzStereoBuffer() {
tgcalls::StreamingAudioRenderer renderer;
std::vector<int16_t> buffer(441 * 2, 0);
bool rendered = renderer.render(constantSource(1000), buffer.data(), 441, 2, 44100);
CHECK_TRUE(rendered);
// The resampler needs a few frames of history before it settles, so judge the second half.
CHECK_TRUE(countOutside(buffer, buffer.size() / 2, 1000, 16) == 0);
}
// HFP routes run at 16 kHz mono.
void TestRendersInto16000HzMonoBuffer() {
tgcalls::StreamingAudioRenderer renderer;
std::vector<int16_t> buffer(160, 0);
bool rendered = renderer.render(constantSource(1000), buffer.data(), 160, 1, 16000);
CHECK_TRUE(rendered);
CHECK_TRUE(countOutside(buffer, buffer.size() / 2, 1000, 16) == 0);
}
// When the playout rate matches the stream, samples pass through untouched.
void TestPassesThrough48000HzUnchanged() {
tgcalls::StreamingAudioRenderer renderer;
std::vector<int16_t> buffer(480 * 2, 0);
auto ramp = [](int16_t *samples, size_t numSamples, size_t numChannels, uint32_t) {
for (size_t i = 0; i < numSamples * numChannels; i++) {
samples[i] = (int16_t)i;
}
};
bool rendered = renderer.render(ramp, buffer.data(), 480, 2, 48000);
CHECK_TRUE(rendered);
size_t mismatches = 0;
for (size_t i = 0; i < buffer.size(); i++) {
if (buffer[i] != (int16_t)i) {
mismatches++;
}
}
CHECK_TRUE(mismatches == 0);
}
// A route can change mid-stream (e.g. speaker -> A2DP); the renderer must follow it.
void TestFollowsRateChangeBetweenCalls() {
tgcalls::StreamingAudioRenderer renderer;
std::vector<int16_t> first(480 * 2, 0);
std::vector<int16_t> second(441 * 2, 0);
CHECK_TRUE(renderer.render(constantSource(1000), first.data(), 480, 2, 48000));
CHECK_TRUE(renderer.render(constantSource(1000), second.data(), 441, 2, 44100));
CHECK_TRUE(countOutside(second, second.size() / 2, 1000, 16) == 0);
}
// The renderer only produces whole 10 ms blocks; a buffer of any other size is refused and
// left as it was, rather than resampled into the wrong length.
void TestRefusesBufferThatIsNotOne10msBlock() {
tgcalls::StreamingAudioRenderer renderer;
std::vector<int16_t> buffer(400 * 2, 7);
bool rendered = renderer.render(constantSource(1000), buffer.data(), 400, 2, 48000);
CHECK_TRUE(!rendered);
CHECK_TRUE(countOutside(buffer, 0, 7, 0) == 0);
}
} // namespace
int main() {
TestRendersInto44100HzStereoBuffer();
TestRendersInto16000HzMonoBuffer();
TestPassesThrough48000HzUnchanged();
TestFollowsRateChangeBetweenCalls();
TestRefusesBufferThatIsNotOne10msBlock();
if (g_failures != 0) {
std::printf("%d failure(s)\n", g_failures);
return 1;
}
std::printf("ok\n");
return 0;
}