Ejemplo n.º 1
0
        public unsafe void SampleBlockConversions()
        {
            SampleBlock reference16Bit;

            using (MediaFoundationReader reference16BitReader = new MediaFoundationReader(TestConstant.ReferenceFilePath16Bit))
            {
                byte[] buffer16Bit = new byte[reference16BitReader.Length];
                int    bytesRead   = reference16BitReader.Read(buffer16Bit, 0, buffer16Bit.Length);
                reference16Bit = new SampleBlock(buffer16Bit, bytesRead, SampleTypeExtensions.FromBitsPerSample(reference16BitReader.WaveFormat.BitsPerSample));
            }

            SampleBlock reference24Bit;

            using (MediaFoundationReader reference24BitReader = new MediaFoundationReader(TestConstant.ReferenceFilePath24Bit))
            {
                byte[] buffer24Bit = new byte[reference24BitReader.Length];
                int    bytesRead   = reference24BitReader.Read(buffer24Bit, 0, buffer24Bit.Length);
                reference24Bit = new SampleBlock(buffer24Bit, bytesRead, SampleTypeExtensions.FromBitsPerSample(reference24BitReader.WaveFormat.BitsPerSample));
            }

            Assert.IsTrue(reference16Bit.Int16Samples == reference24Bit.SamplesInUse);

            SampleBlock reference16BitAsDouble = reference16Bit.ConvertTo(SampleType.Double);
            SampleBlock reference16BitAsQ31    = reference16Bit.ConvertTo(SampleType.Int32);
            SampleBlock reference24BitAsDouble = reference24Bit.ConvertTo(SampleType.Double);
            SampleBlock reference24BitAsQ31    = reference24Bit.ConvertTo(SampleType.Int32);

            Assert.IsTrue(reference16Bit.Int16Samples == reference16BitAsDouble.DoubleSamples);
            Assert.IsTrue(reference16Bit.Int16Samples == reference16BitAsQ31.Int32Samples);
            Assert.IsTrue(reference16Bit.Int16Samples == reference24BitAsDouble.DoubleSamples);
            Assert.IsTrue(reference16Bit.Int16Samples == reference24BitAsQ31.Int32Samples);

            for (int sample = 0; sample < reference16Bit.Int16Samples; ++sample)
            {
                Assert.IsTrue(((int)reference16Bit.Int16s[sample] << TestConstant.ShiftBetween16BitSamplesAndQ31) == reference16BitAsQ31.Int32s[sample]);
                Assert.IsTrue((int)reference16BitAsDouble.Doubles[sample] == reference16BitAsQ31.Int32s[sample]);

                Assert.IsTrue((reference24Bit.GetInt24AsInt32(sample) << TestConstant.ShiftBetween24BitSamplesAndQ31) == reference24BitAsQ31.Int32s[sample]);
                Assert.IsTrue((int)reference24BitAsDouble.Doubles[sample] == reference24BitAsQ31.Int32s[sample]);
            }
        }
Ejemplo n.º 2
0
        private unsafe void VerifyWaveFilesEquivalent(string actualFilePath, string expectedFilePath, double expectedToActualScaleFactor, double sampleMatchTolerance, bool verifySampleSize)
        {
            // load data in files
            SampleBlock actual;
            WaveFormat  actualFormat;

            using (MediaFoundationReader actualReader = new MediaFoundationReader(actualFilePath))
            {
                byte[] buffer    = new byte[actualReader.Length];
                int    bytesRead = actualReader.Read(buffer, 0, buffer.Length);
                actual       = new SampleBlock(buffer, bytesRead, SampleTypeExtensions.FromBitsPerSample(actualReader.WaveFormat.BitsPerSample));
                actualFormat = actualReader.WaveFormat;
            }

            SampleBlock expected;
            WaveFormat  expectedFormat;

            using (MediaFoundationReader expectedReader = new MediaFoundationReader(expectedFilePath))
            {
                byte[] buffer    = new byte[expectedReader.Length];
                int    bytesRead = expectedReader.Read(buffer, 0, buffer.Length);
                expected       = new SampleBlock(buffer, bytesRead, SampleTypeExtensions.FromBitsPerSample(expectedReader.WaveFormat.BitsPerSample));
                expectedFormat = expectedReader.WaveFormat;
            }

            // check data format matches
            Assert.IsTrue(actual.SamplesInUse == expected.SamplesInUse);
            if (verifySampleSize)
            {
                Assert.IsTrue(actual.SampleType == expected.SampleType);
                Assert.IsTrue(actualFormat.AverageBytesPerSecond == expectedFormat.AverageBytesPerSecond);
                Assert.IsTrue(actualFormat.BitsPerSample == expectedFormat.BitsPerSample);
                Assert.IsTrue(actualFormat.BlockAlign == expectedFormat.BlockAlign);
            }
            Assert.IsTrue(actualFormat.Channels == expectedFormat.Channels);
            Assert.IsTrue(actualFormat.Encoding == expectedFormat.Encoding);
            Assert.IsTrue(actualFormat.ExtraSize == expectedFormat.ExtraSize);
            Assert.IsTrue(actualFormat.SampleRate == expectedFormat.SampleRate);

            double largestMismatch          = 0.0;
            double maxExpectedValue         = Math.Pow(2.0, expected.SampleType.BitsPerSample() - 1) - 1;
            double minExpectedValue         = -Math.Pow(2.0, expected.SampleType.BitsPerSample() - 1);
            double mismatchStandardDevation = 0.0;

            using (FileStream samplesStream = new FileStream(String.Format("{0} - {1}.csv", Path.GetFileNameWithoutExtension(actualFilePath), Path.GetFileNameWithoutExtension(expectedFilePath)), FileMode.Create))
            {
                using (StreamWriter samplesWriter = new StreamWriter(samplesStream))
                {
                    samplesWriter.WriteLine("actual,expected");
                    int clippedSamples    = 0;
                    int mismatchedSamples = 0;
                    int samples           = expected.SamplesInUse;
                    for (int sample = 0; sample < samples; ++sample)
                    {
                        // read samples as ints and convert to doubles regardless of sample type in files
                        double actualSample;
                        switch (actual.SampleType)
                        {
                        case SampleType.Int16:
                            actualSample = actual.Int16s[sample];
                            break;

                        case SampleType.Int24:
                            actualSample = actual.GetInt24AsInt32(sample);
                            break;

                        case SampleType.Int32:
                            actualSample = actual.Int32s[sample];
                            break;

                        default:
                            throw new NotSupportedException(String.Format("Unhandled sample type {0}.", expected.SampleType));
                        }
                        actualSample = actualSample / expectedToActualScaleFactor;

                        double expectedSample;
                        switch (expected.SampleType)
                        {
                        case SampleType.Int16:
                            expectedSample = expected.Int16s[sample];
                            break;

                        case SampleType.Int24:
                            expectedSample = expected.GetInt24AsInt32(sample);
                            break;

                        case SampleType.Int32:
                            expectedSample = expected.Int32s[sample];
                            break;

                        default:
                            throw new NotSupportedException(String.Format("Unhandled sample type {0}.", expected.SampleType));
                        }
                        samplesWriter.WriteLine("{0},{1}", actualSample, expectedSample);

                        // it's OK if the expected data is clipped and the actual data isn't
                        if (expectedSample >= maxExpectedValue && actualSample > expectedSample)
                        {
                            ++clippedSamples;
                            continue;
                        }
                        if (expectedSample <= minExpectedValue && actualSample < expectedSample)
                        {
                            ++clippedSamples;
                            continue;
                        }

                        // check samples for equivalence
                        // standard deviation calculation is naive as it assumes the average mismatch is zero
                        double mismatch = Math.Abs(expectedSample - actualSample);
                        mismatchStandardDevation += mismatch * mismatch;
                        if (mismatch > largestMismatch)
                        {
                            largestMismatch = mismatch;
                        }
                        if (mismatch > sampleMatchTolerance)
                        {
                            ++mismatchedSamples;
                        }
                    }

                    mismatchStandardDevation = Math.Sqrt(mismatchStandardDevation / (double)expected.SamplesInUse);
                    this.TestContext.WriteLine("Largest mismatch in samples between {0} and {1} was {2:0.0} with a standard deviation of {3:0.0}.", actualFilePath, expectedFilePath, largestMismatch, mismatchStandardDevation);
                    this.TestContext.WriteLine(">>> {0} samples exceeded threshold of {1:0.0}.", mismatchedSamples, sampleMatchTolerance);
                    Assert.IsTrue(mismatchedSamples == 0, "{0} of {1} ({2:#0.0%}) samples did not match within tolerance {3}.", mismatchedSamples, samples, (double)mismatchedSamples / (double)samples, sampleMatchTolerance);
                    if (verifySampleSize)
                    {
                        Assert.IsTrue(clippedSamples < 0.0001 * samples, "{0} (1:#0.000%) samples were clipped; this is unexpectedly high.", clippedSamples, (double)clippedSamples / (double)samples);
                    }
                }
            }

            // check metadata
            Tag actualMetadata;

            using (FileStream inputMetadataStream = new FileStream(actualFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                using (TagFile inputTagFile = TagFile.Create(new StreamFileAbstraction(inputMetadataStream.Name, inputMetadataStream, inputMetadataStream)))
                {
                    actualMetadata = inputTagFile.Tag;
                }
            }

            Tag expectedMetadata;

            using (FileStream outputMetadataStream = new FileStream(expectedFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                using (TagFile outputTagFile = TagFile.Create(new StreamFileAbstraction(outputMetadataStream.Name, outputMetadataStream, outputMetadataStream)))
                {
                    expectedMetadata = outputTagFile.Tag;
                }
            }

            Assert.AreEqual(expectedMetadata.Album, actualMetadata.Album, String.Format("Expected artist '{0}' but got '{1}'.", expectedMetadata.Album, actualMetadata.Album));
            Assert.AreEqual(expectedMetadata.AmazonId, actualMetadata.AmazonId, String.Format("Expected Amazon ID '{0}' but got '{1}'.", expectedMetadata.AmazonId, actualMetadata.AmazonId));
            Assert.AreEqual(expectedMetadata.Conductor, actualMetadata.Conductor, String.Format("Expected conductor '{0}' but got '{1}'.", expectedMetadata.Conductor, actualMetadata.Conductor));
            Assert.AreEqual(expectedMetadata.Copyright, actualMetadata.Copyright, String.Format("Expected copyright '{0}' but got '{1}'.", expectedMetadata.Copyright, actualMetadata.Copyright));
            Assert.AreEqual(expectedMetadata.Disc, actualMetadata.Disc, String.Format("Expected disc '{0}' but got '{1}'.", expectedMetadata.Disc, actualMetadata.Disc));
            Assert.AreEqual(expectedMetadata.DiscCount, actualMetadata.DiscCount, String.Format("Expected disc count '{0}' but got '{1}'.", expectedMetadata.DiscCount, actualMetadata.DiscCount));
            Assert.AreEqual(expectedMetadata.Grouping, actualMetadata.Grouping, String.Format("Expected grouping '{0}' but got '{1}'.", expectedMetadata.Grouping, actualMetadata.Grouping));
            Assert.AreEqual(expectedMetadata.Lyrics, actualMetadata.Lyrics, String.Format("Expected lyrics '{0}' but got '{1}'.", expectedMetadata.Lyrics, actualMetadata.Lyrics));
            Assert.AreEqual(expectedMetadata.JoinedAlbumArtists, actualMetadata.JoinedAlbumArtists, String.Format("Expected artists '{0}' but got '{1}'.", expectedMetadata.JoinedAlbumArtists, actualMetadata.JoinedAlbumArtists));
            Assert.AreEqual(expectedMetadata.JoinedComposers, actualMetadata.JoinedComposers, String.Format("Expected composers '{0}' but got '{1}'.", expectedMetadata.JoinedComposers, actualMetadata.JoinedComposers));
            Assert.AreEqual(expectedMetadata.JoinedGenres, actualMetadata.JoinedGenres, String.Format("Expected genres '{0}' but got '{1}'.", expectedMetadata.JoinedGenres, actualMetadata.JoinedGenres));
            Assert.AreEqual(expectedMetadata.JoinedPerformersSort, actualMetadata.JoinedPerformersSort, String.Format("Expected performers '{0}' but got '{1}'.", expectedMetadata.JoinedPerformersSort, actualMetadata.JoinedPerformersSort));
            // Pictures is not checked
            Assert.AreEqual(expectedMetadata.Title, actualMetadata.Title, String.Format("Expected title '{0}' but got '{1}'.", expectedMetadata.Title, actualMetadata.Title));
            Assert.AreEqual(expectedMetadata.Track, actualMetadata.Track, String.Format("Expected track '{0}' but got '{1}'.", expectedMetadata.Track, actualMetadata.Track));
            Assert.AreEqual(expectedMetadata.TrackCount, actualMetadata.TrackCount, String.Format("Expected track count '{0}' but got '{1}'.", expectedMetadata.TrackCount, actualMetadata.TrackCount));
            Assert.AreEqual(expectedMetadata.Year, actualMetadata.Year, String.Format("Expected year '{0}' but got '{1}'.", expectedMetadata.Year, actualMetadata.Year));
        }
Ejemplo n.º 3
0
        private WaveStream FilterStream(WaveStream inputStream, out StreamPerformance performance)
        {
            performance = new StreamPerformance();

            // populate filters
            FilterBank forwardTimeFilters = new FilterBank(this.Configuration.Engine.Precision, inputStream.WaveFormat.SampleRate, inputStream.WaveFormat.Channels, this.Configuration.Engine.Q31Adaptive.Q31_32x64_Threshold, this.Configuration.Engine.Q31Adaptive.Q31_64x64_Threshold);
            FilterBank reverseTimeFilters = new FilterBank(this.Configuration.Engine.Precision, inputStream.WaveFormat.SampleRate, inputStream.WaveFormat.Channels, this.Configuration.Engine.Q31Adaptive.Q31_32x64_Threshold, this.Configuration.Engine.Q31Adaptive.Q31_64x64_Threshold);

            foreach (Filter filter in this.Configuration.Filters)
            {
                switch (filter.TimeDirection)
                {
                case TimeDirection.Forward:
                    filter.AddTo(forwardTimeFilters);
                    break;

                case TimeDirection.Reverse:
                    filter.AddTo(reverseTimeFilters);
                    break;

                default:
                    throw new NotSupportedException(String.Format("Unhandled time direction {0}.", filter.TimeDirection));
                }
            }

            // do reverse time pass
            // If the only reverse time filter is the anti-clipping gain then there's nothing to do.
            SampleType dataPathSampleType    = this.Configuration.Engine.Precision == FilterPrecision.Double ? SampleType.Double : SampleType.Int32;
            bool       hasForwardTimeFilters = forwardTimeFilters.FilterCount > 0;
            SampleType outputSampleType      = SampleTypeExtensions.FromBitsPerSample(this.Configuration.Output.BitsPerSample);

            if (reverseTimeFilters.FilterCount > 0 && (this.Stopping == false))
            {
                using (SampleBuffer inputBuffer = new SampleBuffer(inputStream))
                {
                    // set up buffer for output of reverse time pass and dispose input stream as it's no longer needed
                    performance.ReverseBufferCompleteUtc = DateTime.UtcNow;
                    SampleType   reverseTimeSampleType = hasForwardTimeFilters ? dataPathSampleType : outputSampleType;
                    SampleBuffer reverseTimeBuffer     = new SampleBuffer(inputStream.WaveFormat.SampleRate, reverseTimeSampleType.BitsPerSample(), inputStream.WaveFormat.Channels);
                    inputStream.Dispose();

                    // input blocks are disposed as they're processed to limit peak memory consumption (and removed from the input buffer for completeness)
                    SampleBlock recirculatingDataPathBlock = null;
                    for (LinkedListNode <SampleBlock> blockNode = inputBuffer.Blocks.Last; blockNode != null; blockNode = blockNode.Previous, inputBuffer.Blocks.RemoveLast())
                    {
                        using (SampleBlock block = blockNode.Value)
                        {
                            SampleBlock filteredBlock = reverseTimeFilters.FilterReverse(block, dataPathSampleType, reverseTimeSampleType, ref recirculatingDataPathBlock);
                            reverseTimeBuffer.Blocks.AddFirst(filteredBlock);
                        }

                        if (this.Stopping)
                        {
                            break;
                        }
                    }
                    if (recirculatingDataPathBlock != null)
                    {
                        recirculatingDataPathBlock.Dispose();
                    }

                    // prepare to apply forward time pass to output of reverse time pass or just to write output
                    reverseTimeBuffer.RecalculateBlocks();
                    inputStream = reverseTimeBuffer;
                    performance.ReverseTimeCompleteUtc = DateTime.UtcNow;
                }
            }

            // do forward time pass
            if (hasForwardTimeFilters && (this.Stopping == false))
            {
                SampleBuffer outputStream = new SampleBuffer(inputStream.WaveFormat.SampleRate, outputSampleType.BitsPerSample(), inputStream.WaveFormat.Channels);
                SampleBlock  recirculatingDataPathBlock = null;
                if (inputStream is SampleBuffer)
                {
                    SampleBuffer inputBlocks = (SampleBuffer)inputStream;
                    for (LinkedListNode <SampleBlock> blockNode = inputBlocks.Blocks.First; blockNode != null; blockNode = blockNode.Next, inputBlocks.Blocks.RemoveFirst())
                    {
                        using (SampleBlock block = blockNode.Value)
                        {
                            SampleBlock filteredBlock = forwardTimeFilters.Filter(block, dataPathSampleType, outputSampleType, ref recirculatingDataPathBlock);
                            outputStream.Blocks.AddLast(filteredBlock);
                        }

                        if (this.Stopping)
                        {
                            break;
                        }
                    }
                }
                else
                {
                    byte[] inputBuffer = new byte[Constant.SampleBlockSizeInBytes];
                    while (inputStream.CanRead)
                    {
                        int bytesRead = inputStream.Read(inputBuffer, 0, inputBuffer.Length);
                        if (bytesRead < 1)
                        {
                            // workaround for NAudio bug: WaveStream.CanRead is hard coded to true regardless of position
                            break;
                        }

                        using (SampleBlock block = new SampleBlock(inputBuffer, bytesRead, SampleTypeExtensions.FromBitsPerSample(inputStream.WaveFormat.BitsPerSample)))
                        {
                            SampleBlock filteredBlock = forwardTimeFilters.Filter(block, dataPathSampleType, outputSampleType, ref recirculatingDataPathBlock);
                            outputStream.Blocks.AddLast(filteredBlock);
                        }

                        if (this.Stopping)
                        {
                            return(null);
                        }
                    }
                }
                if (recirculatingDataPathBlock != null)
                {
                    recirculatingDataPathBlock.Dispose();
                }

                // release input stream as it's no longer needed and complete output
                inputStream.Dispose();
                outputStream.RecalculateBlocks();
                inputStream = outputStream;
            }

            forwardTimeFilters.Dispose();
            reverseTimeFilters.Dispose();

            performance.CompleteTimeUtc = DateTime.UtcNow;
            return(inputStream);
        }