ABStemPlayer/AudioCore_Tests/TimeStretchEngine_Tests.cs

275 lines
8.4 KiB
C#

using System.Diagnostics;
using AudioCore.Impl;
using AudioCore.Interfaces;
using AudioCore.Models;
namespace AudioCore_Tests;
[TestClass]
public sealed class TimeStretchEngine_Tests
{
private AudioBufferPool _pool = null!;
[TestInitialize]
public void Init()
{
_pool = new AudioBufferPool();
}
private MixedAudioBlock MakeBlock(int frames, int channels = 2, int sampleRate = 44100)
{
var buf = _pool.Rent(frames * channels);
buf.Length = frames * channels;
for (var i = 0; i < buf.Length; i++)
buf.Samples[i] = i * 0.001f;
return new MixedAudioBlock(buf, frames, channels, sampleRate, 0);
}
[TestMethod]
public async Task Process_Returns_Output_For_Speed_1()
{
await using var engine = new RubberBandTimeStretchEngine(_pool, 44100, 2);
var input = MakeBlock(5000);
await engine.Submit(input, CancellationToken.None);
var output = await engine.Receive(CancellationToken.None);
Assert.IsGreaterThan(0, output.Frames);
Assert.AreEqual(2, output.Channels);
Assert.AreEqual(44100, output.SampleRate);
Assert.AreEqual(5000, output.Frames);
foreach (var f in output.Buffer.Span)
{
Assert.IsFalse(float.IsNaN(f));
Assert.IsFalse(float.IsInfinity(f));
}
input.Dispose();
output.Dispose();
}
[TestMethod]
public async Task Process_Respects_Speed_Increase()
{
await using var engine = new RubberBandTimeStretchEngine(_pool, 44100, 2);
using var input = MakeBlock(44100);
using var cts = new CancellationTokenSource();
// -----------------------------
// Phase 1: speed = 1.0
// -----------------------------
var normalFrames = 0;
const int NumberOfIterations = 5;
var submitTask1 = Task.Run(async () =>
{
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
using var ts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, timeout.Token);
for (var i = 0; i < NumberOfIterations; i++)
await engine.Submit(input, ts.Token);
});
var receiveTask1 = Task.Run(async () =>
{
while (true)
{
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2));
using var ts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, timeout.Token);
using var data = await engine.Receive(ts.Token);
if (data.Buffer == null)
break;
normalFrames += data.Frames;
}
});
await Task.WhenAll(submitTask1, receiveTask1);
Debug.WriteLine($"Normal frames: {normalFrames}");
// -----------------------------
// Phase 2: speed = 1.5
// -----------------------------
await engine.Configure(new PlaybackSpeedSettings { Speed = 1.5f }, cts.Token);
var fasterFrames = 0;
var submitTask2 = Task.Run(async () =>
{
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
using var ts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, timeout.Token);
for (var i = 0; i < NumberOfIterations; i++)
await engine.Submit(input, ts.Token);
});
var receiveTask2 = Task.Run(async () =>
{
while (true)
{
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2));
using var ts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, timeout.Token);
using var data = await engine.Receive(ts.Token);
if (data.Buffer == null)
break;
fasterFrames += data.Frames;
}
});
await Task.WhenAll(submitTask2, receiveTask2);
Debug.WriteLine($"Faster frames: {fasterFrames}");
// -----------------------------
// Assertion
// -----------------------------
Assert.IsLessThan(fasterFrames, normalFrames);
cts.Cancel();
}
[TestMethod]
public async Task Process_Respects_Speed_Decrease()
{
await using var engine = new RubberBandTimeStretchEngine(_pool, 44100, 2);
using var input = MakeBlock(44100);
using var cts = new CancellationTokenSource();
// -----------------------------
// Phase 1: speed = 1.0
// -----------------------------
var normalFrames = 0;
const int NumberOfIterations = 5;
var submitTask1 = Task.Run(async () =>
{
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
using var ts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, timeout.Token);
for (var i = 0; i < NumberOfIterations; i++)
await engine.Submit(input, ts.Token);
});
var receiveTask1 = Task.Run(async () =>
{
while (true)
{
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2));
using var ts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, timeout.Token);
using var data = await engine.Receive(ts.Token);
if (data.Buffer == null)
break;
normalFrames += data.Frames;
}
});
await Task.WhenAll(submitTask1, receiveTask1);
Debug.WriteLine($"Normal frames: {normalFrames}");
// -----------------------------
// Phase 2: speed = 0.5
// -----------------------------
await engine.Configure(new PlaybackSpeedSettings { Speed = 0.5f }, cts.Token);
var slowerFrames = 0;
var submitTask2 = Task.Run(async () =>
{
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(10));
using var ts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, timeout.Token);
for (var i = 0; i < NumberOfIterations; i++)
await engine.Submit(input, ts.Token);
});
var receiveTask2 = Task.Run(async () =>
{
while (true)
{
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2));
using var ts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, timeout.Token);
using var data = await engine.Receive(ts.Token);
if (data.Buffer == null)
break;
slowerFrames += data.Frames;
}
});
await Task.WhenAll(submitTask2, receiveTask2);
Debug.WriteLine($"Slower frames: {slowerFrames}");
// -----------------------------
// Assertion
// -----------------------------
Assert.IsLessThan(slowerFrames, normalFrames);
cts.Cancel();
}
[TestMethod]
public async Task Engine_Restarts_On_Speed_Change()
{
await using var engine = new RubberBandTimeStretchEngine(_pool, 44100, 2);
using var cts = new CancellationTokenSource();
await engine.Configure(new PlaybackSpeedSettings { Speed = 1f }, cts.Token);
var input = MakeBlock(44100);
await engine.Submit(input, cts.Token);
var before = await engine.Receive(cts.Token);
await engine.Configure(new PlaybackSpeedSettings { Speed = 0.75f }, cts.Token);
await engine.Submit(input, cts.Token);
var after = await engine.Receive(cts.Token);
Assert.AreNotEqual(0, before.Frames);
Assert.AreNotEqual(0, after.Frames);
Assert.AreNotEqual(before.Frames, after.Frames);
cts.Cancel();
}
[TestMethod]
public async Task Dispose_Kills_FFmpeg()
{
var engine = new RubberBandTimeStretchEngine(_pool, 44100, 2);
using var cts = new CancellationTokenSource();
var ffField = typeof(RubberBandTimeStretchEngine)
.GetField("_ff", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
var ff = (Process?)ffField!.GetValue(engine);
var pid = ff?.Id ?? -1;
await engine.DisposeAsync();
var exists = Process.GetProcesses().Any(p =>
{
try { return p.Id == pid; }
catch { return false; }
});
Assert.IsFalse(exists);
cts.Cancel();
}
}