public async Task ShouldNotLoseAudioSamplesInCaseIfExceptionIsThrown() { var audioService = new SoundFingerprintingAudioService(); var modelService = new InMemoryModelService(); int count = 10, found = 0, didNotPassThreshold = 0, thresholdVotes = 4, testWaitTime = 40000, fingerprintsCount = 0, errored = 0; var data = GenerateRandomAudioChunks(count, 1); var concatenated = Concatenate(data); var hashes = await FingerprintCommandBuilder.Instance .BuildFingerprintCommand() .From(concatenated) .UsingServices(audioService) .Hash(); modelService.Insert(new TrackInfo("312", "Bohemian Rhapsody", "Queen"), hashes); var started = DateTime.Now; var resultEntries = new List<ResultEntry>(); var collection = SimulateRealtimeQueryData(data, true, TimeSpan.FromSeconds); var cancellationTokenSource = new CancellationTokenSource(testWaitTime); var offlineStorage = new OfflineStorage(Path.GetTempPath()); var restoreCalled = new bool[1]; double processed = await new RealtimeQueryCommand(FingerprintCommandBuilder.Instance, new FaultyQueryService(count, QueryFingerprintService.Instance)) .From(collection) .WithRealtimeQueryConfig(config => { config.SuccessCallback = entry => { Interlocked.Increment(ref found); resultEntries.Add(entry); }; config.QueryFingerprintsCallback = fingerprints => Interlocked.Increment(ref fingerprintsCount); config.DidNotPassFilterCallback = entry => Interlocked.Increment(ref didNotPassThreshold); config.ErrorCallback = (exception, timedHashes) => { Interlocked.Increment(ref errored); offlineStorage.Save(timedHashes); }; config.ResultEntryFilter = new QueryMatchLengthFilter(10); config.RestoredAfterErrorCallback = () => restoreCalled[0] = true; config.PermittedGap = 1.48d; config.ThresholdVotes = thresholdVotes; config.DowntimeHashes = offlineStorage; config.DowntimeCapturePeriod = 3d; return config; }) .UsingServices(modelService) .Query(cancellationTokenSource.Token); Assert.AreEqual(count, errored); Assert.AreEqual(20, fingerprintsCount); Assert.IsTrue(restoreCalled[0]); Assert.AreEqual(1, found); var resultEntry = resultEntries[0]; double jitterLength = 5 * 10240 / 5512d; Assert.AreEqual(0d, started.AddSeconds(jitterLength + resultEntry.TrackMatchStartsAt).Subtract(resultEntry.MatchedAt).TotalSeconds, 2d); Assert.AreEqual(1, didNotPassThreshold); Assert.AreEqual((count + 10) * 10240 / 5512d, processed, 0.2); }
public async Task ShouldNotLoseAudioSamplesInCaseIfExceptionIsThrown() { var audioService = new SoundFingerprintingAudioService(); var modelService = new InMemoryModelService(); const double minSizeChunk = 10240d / 5512; // length in seconds of one query chunk ~1.8577 const double totalTrackLength = 210; // length of the track 3 minutes 30 seconds. const double jitterLength = 10; double totalQueryLength = totalTrackLength + 2 * jitterLength; int trackCount = (int)(totalTrackLength / minSizeChunk), fingerprintsCount = 0, errored = 0, didNotPassThreshold = 0, jitterChunks = 2; var start = new DateTime(2021, 5, 1, 0, 0, 0, DateTimeKind.Utc); var data = GenerateRandomAudioChunks(trackCount, seed: 1, start); var concatenated = Concatenate(data); var hashes = await FingerprintCommandBuilder.Instance .BuildFingerprintCommand() .From(concatenated) .UsingServices(audioService) .Hash(); modelService.Insert(new TrackInfo("312", "Bohemian Rhapsody", "Queen"), hashes); var resultEntries = new List <ResultEntry>(); var collection = SimulateRealtimeQueryData(data, jitterLength); var offlineStorage = new OfflineStorage(Path.GetTempPath()); var restoreCalled = new bool[1]; double processed = await new RealtimeQueryCommand(FingerprintCommandBuilder.Instance, new FaultyQueryService(faultyCounts: trackCount + jitterChunks - 1, QueryFingerprintService.Instance)) .From(collection) .WithRealtimeQueryConfig(config => { config.SuccessCallback = entry => { resultEntries.Add(entry); }; config.DidNotPassFilterCallback = _ => Interlocked.Increment(ref didNotPassThreshold); config.ErrorCallback = (_, timedHashes) => { Interlocked.Increment(ref errored); offlineStorage.Save(timedHashes); }; config.ResultEntryFilter = new TrackRelativeCoverageLengthEntryFilter(0.4, waitTillCompletion: true); config.RestoredAfterErrorCallback = () => restoreCalled[0] = true; config.DowntimeHashes = offlineStorage; // store the other half of the fingerprints in the downtime hashes storage config.DowntimeCapturePeriod = totalQueryLength / 2; // store half of the fingerprints in the offline storage return(config); }) .Intercept(fingerprints => { Interlocked.Increment(ref fingerprintsCount); return(fingerprints); }) .UsingServices(modelService) .Query(CancellationToken.None); Assert.AreEqual(totalQueryLength, processed, 1); Assert.AreEqual(trackCount + jitterChunks - 1, errored); Assert.AreEqual(trackCount, fingerprintsCount - jitterChunks); Assert.IsTrue(restoreCalled[0]); Assert.AreEqual(0, didNotPassThreshold); Assert.AreEqual(1, resultEntries.Count); var result = resultEntries.First(); Assert.AreEqual(totalTrackLength, result.Coverage.TrackCoverageWithPermittedGapsLength, 1); Assert.IsTrue(Math.Abs(start.Subtract(result.MatchedAt).TotalSeconds) < 2, $"Matched At {result.MatchedAt:o}"); }