mirror of
https://github.com/unclshura/ABStemPlayer.git
synced 2026-08-07 00:43:38 +00:00
275 lines
8.4 KiB
C#
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();
|
|
}
|
|
}
|