Skip to content

Commit e0ea1b5

Browse files
committed
Merge branch 'develop'
* develop: Adjusting comments. Adding release notes. Adjusting `AVResultEntryFilter` implementation classes, making sure we adhere to the same principles across both `ResultEntryFilter` and `OngoingResultEntryFilter`. Adding `ChainedRealtimeEntryFilter` which replaces `OngoingRealtimeResultEntryFilter`. Making sure we are not drifting in time with cached realtime tail.
2 parents 65f0d5d + c262106 commit e0ea1b5

17 files changed

+177
-98
lines changed

src/SoundFingerprinting.Tests/Properties/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@
1111
[assembly: AssemblyCulture("")]
1212
[assembly: ComVisible(false)]
1313
[assembly: Guid("4cac962e-ebc5-4006-a1e0-7ffb3e2483c2")]
14-
[assembly: AssemblyVersion("11.0.0.100")]
15-
[assembly: AssemblyInformationalVersion("11.0.0.100")]
14+
[assembly: AssemblyVersion("11.1.0.100")]
15+
[assembly: AssemblyInformationalVersion("11.1.0.100")]

src/SoundFingerprinting.Tests/Unit/Audio/RealtimeAudioSamplesAggregatorTest.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,21 @@ public void ShouldIgnoreEmptyDataArraysIfTheyArrive()
2828
public void ShouldPrefixSamples()
2929
{
3030
int previousLength = 0;
31-
int minSamplesPerFingerprint = 10_176, sampleRate = 5512, stride = 256, lengthInSeconds = 10;
31+
int minSamplesPerFingerprint = new DefaultFingerprintConfiguration().SamplesPerFingerprint, sampleRate = 5512, stride = 256, lengthInSeconds = 10;
3232
var realtimeAggregator = new RealtimeAudioSamplesAggregator(minSamplesPerFingerprint, new IncrementalStaticStride(stride));
33-
for (int i = 0; i < 10; ++i)
33+
var relativeTo = DateTime.UnixEpoch;
34+
for (int i = 0; i < 200; ++i)
3435
{
35-
var samples = realtimeAggregator.Aggregate(TestUtilities.GenerateRandomAudioSamples(lengthInSeconds * sampleRate));
36+
var expected = relativeTo.AddSeconds(lengthInSeconds * i);
37+
var samples = realtimeAggregator.Aggregate(TestUtilities.GenerateRandomAudioSamples(lengthInSeconds * sampleRate, expected));
3638
Assert.IsNotNull(samples);
3739
logger.LogInformation("Samples duration: {Duration:0.00}", samples.Duration);
3840
if (i > 0)
3941
{
4042
Assert.AreEqual(lengthInSeconds + (float)(minSamplesPerFingerprint - stride) / sampleRate + (float)((previousLength - minSamplesPerFingerprint) % stride) / sampleRate, samples.Duration, 0.00001, $"Iteration {i}");
4143
Assert.AreEqual(lengthInSeconds - samples.TimeOffset, samples.Duration, 0.00001);
44+
double totalSeconds = expected.Subtract(samples.RelativeTo).TotalSeconds;
45+
Assert.IsTrue(totalSeconds < (double)minSamplesPerFingerprint / sampleRate);
4246
}
4347

4448
previousLength = samples.Samples.Length;
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
namespace SoundFingerprinting.Tests.Unit.Command;
2+
3+
using System;
4+
using NUnit.Framework;
5+
using SoundFingerprinting.Command;
6+
using SoundFingerprinting.DAO;
7+
using SoundFingerprinting.DAO.Data;
8+
using SoundFingerprinting.Query;
9+
10+
[TestFixture]
11+
public class ChainedRealtimeEntryFilterTest
12+
{
13+
[TestCase(0.2, true, false)]
14+
[TestCase(0.4, true, false)]
15+
[TestCase(0.4, false, true)]
16+
[TestCase(0.6, false, true)]
17+
public void ShouldWorkAsExpected(double runnigCoverage, bool canContinueInTheNextQuery, bool expected)
18+
{
19+
var filter1 = new TrackRelativeCoverageEntryFilter(0.5, waitTillCompletion: true);
20+
var filter2 = new TrackCoverageLengthEntryFilter(10d, waitTillCompletion: true);
21+
var filter3 = new ChainedRealtimeEntryFilter([filter1, filter2]);
22+
23+
var track = new TrackData("id", "isrc", "title", 30, new ModelReference<uint>(1));
24+
var coverage = TestUtilities.GetCoverage(track.Length * runnigCoverage, []);
25+
var resultEntry = new ResultEntry(track, 1, DateTime.UnixEpoch, coverage);
26+
27+
bool actual = filter3.Pass(new AVResultEntry(resultEntry, resultEntry), canContinueInTheNextQuery);
28+
29+
Assert.That(actual, Is.EqualTo(expected));
30+
}
31+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
namespace SoundFingerprinting.Tests.Unit.Command;
2+
3+
using System;
4+
using NUnit.Framework;
5+
using SoundFingerprinting.Command;
6+
using SoundFingerprinting.DAO;
7+
using SoundFingerprinting.DAO.Data;
8+
using SoundFingerprinting.Query;
9+
10+
[TestFixture]
11+
public class TrackRelativeCoverageEntryFilterTest
12+
{
13+
[TestCase(0.4, true, 0.2, true, false)]
14+
[TestCase(0.4, true, 0.5, true, false)]
15+
[TestCase(0.4, true, 0.7, true, false)]
16+
[TestCase(0.4, true, 0.9, false, true)]
17+
[TestCase(0.4, true, 0.3, false, false)]
18+
[TestCase(0.4, true, 0.5, false, true)]
19+
public void ShouldWorkAsExpected(double relativeCoverage, bool waitTillCompletion, double runningCoverage, bool canContinueInTheNextQuery, bool expected)
20+
{
21+
var filter = new TrackRelativeCoverageEntryFilter(relativeCoverage, waitTillCompletion);
22+
var track = new TrackData("id", "isrc", "title", 30, new ModelReference<uint>(1));
23+
var coverage = TestUtilities.GetCoverage(track.Length * runningCoverage, []);
24+
var resultEntry = new ResultEntry(track, 1, DateTime.UnixEpoch, coverage);
25+
26+
bool actual = filter.Pass(new AVResultEntry(resultEntry, resultEntry), canContinueInTheNextQuery);
27+
28+
Assert.That(actual, Is.EqualTo(expected));
29+
}
30+
}

src/SoundFingerprinting.Tests/Unit/Query/RealtimeQueryCommandTest.cs

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public async Task RealtimeQueryShouldMatchOnlySelectedMetaFieldsFilters()
6262
.From(SimulateRealtimeAudioQueryData(data, jitterLength: 0))
6363
.WithRealtimeQueryConfig(config =>
6464
{
65-
config.ResultEntryFilter = new TrackMatchLengthEntryFilter(15d);
65+
config.ResultEntryFilter = new TrackCoverageLengthEntryFilter(15d, waitTillCompletion: false);
6666
config.SuccessCallback = _ => Interlocked.Increment(ref foundWithWrongClusters);
6767
config.YesMetaFieldsFilter = new Dictionary<string, string> {{"country", "CANADA"}};
6868
return config;
@@ -75,7 +75,7 @@ public async Task RealtimeQueryShouldMatchOnlySelectedMetaFieldsFilters()
7575
.From(SimulateRealtimeAudioQueryData(data, jitterLength: 0))
7676
.WithRealtimeQueryConfig(config =>
7777
{
78-
config.ResultEntryFilter = new TrackMatchLengthEntryFilter(15d);
78+
config.ResultEntryFilter = new TrackCoverageLengthEntryFilter(15d, waitTillCompletion: false);
7979
config.SuccessCallback = _ => Interlocked.Increment(ref foundWithClusters);
8080
config.YesMetaFieldsFilter = new Dictionary<string, string> {{"country", "USA"}};
8181
return config;
@@ -160,7 +160,7 @@ await QueryCommandBuilder.Instance
160160
.From(collection)
161161
.WithRealtimeQueryConfig(config =>
162162
{
163-
config.ResultEntryFilter = new TrackRelativeCoverageLengthEntryFilter(0.2d, waitTillCompletion: true);
163+
config.ResultEntryFilter = new TrackRelativeCoverageEntryFilter(0.2d, waitTillCompletion: true);
164164
config.QueryConfiguration.Audio.Stride = new IncrementalStaticStride(staticStride);
165165
config.SuccessCallback = _ =>
166166
{
@@ -237,8 +237,11 @@ public async Task ShouldQueryInRealtime()
237237
{
238238
config.QueryConfiguration.Audio.Stride = new IncrementalRandomStride(256, 512);
239239
config.QueryConfiguration.Audio.PermittedGap = 2;
240-
config.ResultEntryFilter = new TrackMatchLengthEntryFilter(queryMatchLength);
241-
config.OngoingResultEntryFilter = new OngoingRealtimeResultEntryFilter(minCoverage: 0.2d, minTrackLength: 1d);
240+
config.ResultEntryFilter = new TrackCoverageLengthEntryFilter(queryMatchLength, waitTillCompletion: false);
241+
config.OngoingResultEntryFilter = new ChainedRealtimeEntryFilter([
242+
new TrackRelativeCoverageEntryFilter(0.2d, waitTillCompletion: false),
243+
new TrackCoverageLengthEntryFilter(1d, waitTillCompletion: false)
244+
]);
242245
config.SuccessCallback = result =>
243246
{
244247
foreach (var (entry, _) in result.ResultEntries)
@@ -325,15 +328,16 @@ public async Task ShouldNotLoseAudioSamplesInCaseIfExceptionIsThrown()
325328
var collection = SimulateRealtimeAudioQueryData(data, jitterLength);
326329
var offlineStorage = new OfflineStorage(Path.GetTempPath());
327330
var restoreCalled = new bool[1];
331+
var loggerFactory = new NullLoggerFactory();
328332
double processed = await new RealtimeQueryCommand(FingerprintCommandBuilder.Instance, new QueryCommandBuilder(FingerprintCommandBuilder.Instance,
329-
new FaultyQueryService(faultyCounts: trackCount + jitterChunks - 1, QueryFingerprintService.Instance), new NullLoggerFactory()), new NullLoggerFactory())
333+
new FaultyQueryService(faultyCounts: trackCount + jitterChunks - 1, QueryFingerprintService.Instance), loggerFactory), loggerFactory)
330334
.From(collection)
331335
.WithRealtimeQueryConfig(config =>
332336
{
333337
config.SuccessCallback = entry => resultEntries.AddRange(entry.ResultEntries);
334338
config.DidNotPassFilterCallback = _ => Interlocked.Increment(ref didNotPassThreshold);
335339
config.ErrorCallback = (_, _) => Interlocked.Increment(ref errored);
336-
config.ResultEntryFilter = new TrackRelativeCoverageLengthEntryFilter(0.4, waitTillCompletion: true);
340+
config.ResultEntryFilter = new TrackRelativeCoverageEntryFilter(0.4, waitTillCompletion: true);
337341
config.RestoredAfterErrorCallback = () => restoreCalled[0] = true;
338342
config.OfflineStorage = offlineStorage; // store the other half of the fingerprints in the downtime hashes storage
339343
config.DelayStrategy = new NoDelayStrategy();
@@ -444,7 +448,7 @@ await QueryCommandBuilder.Instance.BuildRealtimeQueryCommand()
444448
.WithRealtimeQueryConfig(config =>
445449
{
446450
config.SuccessCallback = entry => entries.AddRange(entry.ResultEntries);
447-
config.ResultEntryFilter = new TrackRelativeCoverageLengthEntryFilter(0.8d);
451+
config.ResultEntryFilter = new TrackRelativeCoverageEntryFilter(0.8d, waitTillCompletion: true);
448452
config.QueryConfiguration.Audio.Stride = new IncrementalStaticStride(2048);
449453
return config;
450454
})
@@ -497,7 +501,7 @@ await QueryCommandBuilder.Instance.BuildRealtimeQueryCommand()
497501
.WithRealtimeQueryConfig(config =>
498502
{
499503
config.QueryConfiguration.Audio.Stride = new IncrementalRandomStride(256, 512, seed: 123);
500-
config.ResultEntryFilter = new TrackRelativeCoverageLengthEntryFilter(0.5, waitTillCompletion: true);
504+
config.ResultEntryFilter = new TrackRelativeCoverageEntryFilter(0.5, waitTillCompletion: true);
501505
config.SuccessCallback = _ => success.AddRange(_.ResultEntries);
502506
config.DidNotPassFilterCallback = _ => didNotPass.AddRange(_.ResultEntries);
503507
return config;
@@ -591,14 +595,18 @@ public async Task ShouldQueryBothAudioAndVideo()
591595

592596
var successMatches = new List<AVResultEntry>();
593597
var didNotGetToContiguousQueryMatchLengthMatch = new List<AVResultEntry>();
594-
double processed = await QueryCommandBuilder.Instance
598+
double processed = await QueryCommandBuilder
599+
.Instance
595600
.BuildRealtimeQueryCommand()
596601
.From(collection)
597602
.WithRealtimeQueryConfig(config =>
598603
{
599604
config.QueryConfiguration.Audio.Stride = new IncrementalRandomStride(256, 512, seed: 123);
600605
config.ResultEntryFilter = new CompletedRealtimeMatchResultEntryFilter();
601-
config.OngoingResultEntryFilter = new OngoingRealtimeResultEntryFilter(minCoverage: 0.2d, minTrackLength: 1d);
606+
config.OngoingResultEntryFilter = new ChainedRealtimeEntryFilter([
607+
new TrackRelativeCoverageEntryFilter(0.2d, waitTillCompletion: false),
608+
new TrackCoverageLengthEntryFilter(2d, waitTillCompletion: false)
609+
]);
602610
config.SuccessCallback = result =>
603611
{
604612
successCallbackCalls++;
@@ -610,7 +618,11 @@ public async Task ShouldQueryBothAudioAndVideo()
610618
didNotGetToContiguousQueryMatchLengthMatch.AddRange(result.ResultEntries);
611619
};
612620

613-
config.OngoingCallback = _ => { Interlocked.Add(ref ongoingCalls, _.Count()); };
621+
config.OngoingCallback = _ =>
622+
{
623+
Interlocked.Add(ref ongoingCalls, _.Count());
624+
};
625+
614626
config.ErrorCallback = (error, _) => throw error;
615627
return config;
616628
})
@@ -739,7 +751,7 @@ await QueryCommandBuilder.Instance
739751
.WithRealtimeQueryConfig(config =>
740752
{
741753
config.QueryConfiguration.Audio.Stride = new IncrementalRandomStride(256, 512, seed: 123);
742-
config.ResultEntryFilter = new TrackRelativeCoverageLengthEntryFilter(coverage: 0.2, waitTillCompletion: true);
754+
config.ResultEntryFilter = new TrackRelativeCoverageEntryFilter(coverage: 0.2, waitTillCompletion: true);
743755
config.SuccessCallback = _ => success.AddRange(_.ResultEntries);
744756
config.DidNotPassFilterCallback = _ => didNotPass.AddRange(_.ResultEntries);
745757
config.ErrorCallback = (e, _) => logger.LogError(e, "An error occured while querying stream");

src/SoundFingerprinting.Tests/Unit/Query/StatefulRealtimeResultEntryAggregatorTest.cs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public class StatefulRealtimeResultEntryAggregatorTest
3131
public void ShouldNotFailWithNullObjectPass()
3232
{
3333
var aggregator = new StatefulRealtimeResultEntryAggregator(
34-
new TrackMatchLengthEntryFilter(5d),
34+
new TrackCoverageLengthEntryFilter(5d, waitTillCompletion: false),
3535
new NoPassRealtimeResultEntryFilter(),
3636
new AvResultEntryCompletionStrategy(new ResultEntryCompletionStrategy(3d), new ResultEntryCompletionStrategy(1.75d)),
3737
new ResultEntryConcatenator(loggerFactory, false),
@@ -49,7 +49,7 @@ public void ShouldWaitAsTrackLengthPermits()
4949
{
5050
double permittedGap = 5d;
5151
var aggregator = new StatefulRealtimeResultEntryAggregator(
52-
new TrackMatchLengthEntryFilter(10d),
52+
new TrackCoverageLengthEntryFilter(10d, waitTillCompletion: false),
5353
new NoPassRealtimeResultEntryFilter(),
5454
new AvResultEntryCompletionStrategy(new ResultEntryCompletionStrategy(0d), new ResultEntryCompletionStrategy(0d)),
5555
new ResultEntryConcatenator(loggerFactory, false),
@@ -86,7 +86,8 @@ public void ShouldWaitAsTrackLengthPermits()
8686
public void ShouldMergeResults()
8787
{
8888
double permittedGap = 2d;
89-
var aggregator = new StatefulRealtimeResultEntryAggregator(new TrackMatchLengthEntryFilter(5d),
89+
var aggregator = new StatefulRealtimeResultEntryAggregator(
90+
new TrackCoverageLengthEntryFilter(5d, waitTillCompletion: false),
9091
new NoPassRealtimeResultEntryFilter(),
9192
new AvResultEntryCompletionStrategy(new ResultEntryCompletionStrategy(3d), new ResultEntryCompletionStrategy(1.75d)),
9293
new ResultEntryConcatenator(loggerFactory, false),
@@ -107,7 +108,7 @@ public void ShouldMergeResults()
107108
var randomHashes = TestUtilities.GetRandomHashes(1);
108109
for (int i = 0; i < 10; ++i)
109110
{
110-
var entry = new ResultEntry(GetTrack(trackLength), 0, DateTime.Now, TestUtilities.GetMatchedWith(new[] { 0 }, new[] { i }).GetCoverages(QueryPathReconstructionStrategyType.MultipleBestPaths, queryLength, trackLength, fingerprintLength, permittedGap).First());
111+
var entry = new ResultEntry(GetTrack(trackLength), 0, DateTime.Now, TestUtilities.GetMatchedWith([0], [i]).GetCoverages(QueryPathReconstructionStrategyType.MultipleBestPaths, queryLength, trackLength, fingerprintLength, permittedGap).First());
111112
var audioResult = new QueryResult(new[] { entry }, randomHashes, QueryCommandStats.Zero());
112113
var avEntry = new AVQueryResult(audioResult, null, new AVHashes(randomHashes, null), new AVQueryCommandStats(QueryCommandStats.Zero(), null));
113114
var aggregated = aggregator.Consume(avEntry);
@@ -179,7 +180,7 @@ await QueryCommandBuilder.Instance
179180
config.QueryConfiguration.Audio.Stride = stride;
180181
config.ResultEntryFilter = new NoPassRealtimeResultEntryFilter();
181182
config.OngoingResultEntryFilter = new NoPassRealtimeResultEntryFilter();
182-
config.DidNotPassFilterCallback = (avQueryResult) =>
183+
config.DidNotPassFilterCallback = avQueryResult =>
183184
{
184185
results.AddRange(avQueryResult.ResultEntries.Select(_ => _.Audio));
185186
};
@@ -204,8 +205,6 @@ await QueryCommandBuilder.Instance
204205
Assert.AreEqual(p.Expected, p.Actual, 0.00001);
205206
}
206207
}
207-
208-
209208

210209
private static void SimulateEmptyResults(IRealtimeResultEntryAggregator aggregator, ICollection<AVResultEntry> success, ICollection<AVResultEntry> filtered)
211210
{

0 commit comments

Comments
 (0)