Browse Source

merge

pull/1/head
Mark Heath 14 years ago
parent
commit
560b97e376
  1. 2
      NAudio/NAudio.csproj
  2. 4
      NAudio/Properties/AssemblyInfo.cs
  3. 180
      NAudio/Wave/SampleProviders/MultiplexingSampleProvider.cs
  4. 191
      NAudio/Wave/WaveProviders/MultiplexingWaveProvider.cs
  5. 10
      NAudioTests/NAudioTests.csproj
  6. 29
      NAudioTests/StopwatchExtensions.cs
  7. 276
      NAudioTests/WaveStreams/MultiplexingSampleProviderTests.cs
  8. 339
      NAudioTests/WaveStreams/MultiplexingWaveProviderTests.cs
  9. 1
      NAudioTests/packages.config

2
NAudio/NAudio.csproj

@ -99,6 +99,7 @@
<Compile Include="CoreAudioApi\Interfaces\IAudioRenderClient.cs" />
<Compile Include="CoreAudioApi\MMDeviceCollection.cs" />
<Compile Include="CoreAudioApi\PropertyKeys.cs" />
<Compile Include="Wave\SampleProviders\MultiplexingSampleProvider.cs" />
<Compile Include="Wave\WaveInputs\WasapiCapture.cs" />
<Compile Include="Dmo\AudioMediaSubtypes.cs" />
<Compile Include="Dmo\DmoDescriptor.cs" />
@ -392,6 +393,7 @@
<Compile Include="Wave\SampleProviders\SampleToWaveProvider.cs" />
<Compile Include="Wave\WaveProviders\BufferedWaveProvider.cs" />
<Compile Include="Wave\SampleProviders\MonoToStereoSampleProvider.cs" />
<Compile Include="Wave\WaveProviders\MultiplexingWaveProvider.cs" />
<Compile Include="Wave\WaveProviders\StereoToMonoProvider16.cs" />
<Compile Include="Wave\WaveProviders\MonoToStereoProvider16.cs" />
<Compile Include="Wave\WaveProviders\WaveFloatTo16Provider.cs" />

4
NAudio/Properties/AssemblyInfo.cs

@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
//
// You can specify all the values or you can default the Revision and Build Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("1.5.5.0")]
[assembly: AssemblyFileVersion("1.5.5.0")]
[assembly: AssemblyVersion("1.5.6.0")]
[assembly: AssemblyFileVersion("1.5.6.0")]

180
NAudio/Wave/SampleProviders/MultiplexingSampleProvider.cs

@ -0,0 +1,180 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace NAudio.Wave.SampleProviders
{
/// <summary>
/// Allows any number of inputs to be patched to outputs
/// Uses could include swapping left and right channels, turning mono into stereo,
/// feeding different input sources to different soundcard outputs etc
/// </summary>
public class MultiplexingSampleProvider : ISampleProvider
{
private readonly IList<ISampleProvider> inputs;
private readonly WaveFormat waveFormat;
private readonly int outputChannelCount;
private readonly int inputChannelCount;
private readonly List<int> mappings;
/// <summary>
/// Creates a multiplexing sample provider, allowing re-patching of input channels to different
/// output channels
/// </summary>
/// <param name="inputs">Input sample providers. Must all be of the same sample rate, but can have any number of channels</param>
/// <param name="numberOfOutputChannels">Desired number of output channels.</param>
public MultiplexingSampleProvider(IEnumerable<ISampleProvider> inputs, int numberOfOutputChannels)
{
this.inputs = new List<ISampleProvider>(inputs);
this.outputChannelCount = numberOfOutputChannels;
if (this.inputs.Count == 0)
{
throw new ArgumentException("You must provide at least one input");
}
if (numberOfOutputChannels < 1)
{
throw new ArgumentException("You must provide at least one output");
}
foreach (var input in this.inputs)
{
if (this.waveFormat == null)
{
if (input.WaveFormat.Encoding != WaveFormatEncoding.IeeeFloat)
{
throw new ArgumentException("Only 32 bit float is supported");
}
this.waveFormat = WaveFormat.CreateIeeeFloatWaveFormat(input.WaveFormat.SampleRate, numberOfOutputChannels);
}
else
{
if (input.WaveFormat.BitsPerSample != this.waveFormat.BitsPerSample)
{
throw new ArgumentException("All inputs must have the same bit depth");
}
if (input.WaveFormat.SampleRate != this.waveFormat.SampleRate)
{
throw new ArgumentException("All inputs must have the same sample rate");
}
}
inputChannelCount += input.WaveFormat.Channels;
}
mappings = new List<int>();
for (int n = 0; n < outputChannelCount; n++)
{
mappings.Add(n % inputChannelCount);
}
}
/// <summary>
/// persistent temporary buffer to prevent creating work for garbage collector
/// </summary>
private float[] tempBuffer;
private float[] GetBuffer(int size)
{
if (tempBuffer == null || tempBuffer.Length < size)
{
tempBuffer = new float[Math.Max(size, this.WaveFormat.AverageBytesPerSecond) / 4];
}
return tempBuffer;
}
/// <summary>
/// Reads samples from this sample provider
/// </summary>
/// <param name="buffer">Buffer to be filled with sample data</param>
/// <param name="offset">Offset into buffer to start writing to, usually 0</param>
/// <param name="count">Number of samples required</param>
/// <returns>Number of samples read</returns>
public int Read(float[] buffer, int offset, int count)
{
int sampleFramesRequested = count / outputChannelCount;
int inputOffset = 0;
int sampleFramesRead = 0;
// now we must read from all inputs, even if we don't need their data, so they stay in sync
foreach (var input in inputs)
{
int samplesRequired = sampleFramesRequested * input.WaveFormat.Channels;
float[] inputBuffer = GetBuffer(samplesRequired);
int samplesRead = input.Read(inputBuffer, 0, samplesRequired);
sampleFramesRead = Math.Max(sampleFramesRead, samplesRead / input.WaveFormat.Channels);
for (int n = 0; n < input.WaveFormat.Channels; n++)
{
int inputIndex = inputOffset + n;
for (int outputIndex = 0; outputIndex < outputChannelCount; outputIndex++)
{
if (mappings[outputIndex] == inputIndex)
{
int inputBufferOffset = n;
int outputBufferOffset = offset + outputIndex;
int sample = 0;
while (sample < sampleFramesRequested && inputBufferOffset < samplesRead)
{
buffer[outputBufferOffset] = inputBuffer[inputBufferOffset];
outputBufferOffset += outputChannelCount;
inputBufferOffset += input.WaveFormat.Channels;
sample++;
}
// clear the end
while (sample < sampleFramesRequested)
{
buffer[outputBufferOffset] = 0;
outputBufferOffset += outputChannelCount;
sample++;
}
}
}
}
inputOffset += input.WaveFormat.Channels;
}
return sampleFramesRead * outputChannelCount;
}
/// <summary>
/// The output WaveFormat for this SampleProvider
/// </summary>
public WaveFormat WaveFormat
{
get { return waveFormat; }
}
/// <summary>
/// Connects a specified input channel to an output channel
/// </summary>
/// <param name="inputChannel">Input Channel index (zero based). Must be less than InputChannelCount</param>
/// <param name="outputChannel">Output Channel index (zero based). Must be less than OutputChannelCount</param>
public void ConnectInputToOutput(int inputChannel, int outputChannel)
{
if (inputChannel < 0 || inputChannel >= InputChannelCount)
{
throw new ArgumentException("Invalid input channel");
}
if (outputChannel < 0 || outputChannel >= OutputChannelCount)
{
throw new ArgumentException("Invalid output channel");
}
mappings[outputChannel] = inputChannel;
}
/// <summary>
/// The number of input channels. Note that this is not the same as the number of input wave providers. If you pass in
/// one stereo and one mono input provider, the number of input channels is three.
/// </summary>
public int InputChannelCount
{
get { return inputChannelCount; }
}
/// <summary>
/// The number of output channels, as specified in the constructor.
/// </summary>
public int OutputChannelCount
{
get { return outputChannelCount; }
}
}
}

191
NAudio/Wave/WaveProviders/MultiplexingWaveProvider.cs

@ -0,0 +1,191 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace NAudio.Wave
{
/// <summary>
/// Allows any number of inputs to be patched to outputs
/// Uses could include swapping left and right channels, turning mono into stereo,
/// feeding different input sources to different soundcard outputs etc
/// </summary>
public class MultiplexingWaveProvider : IWaveProvider
{
private readonly IList<IWaveProvider> inputs;
private readonly WaveFormat waveFormat;
private readonly int outputChannelCount;
private readonly int inputChannelCount;
private readonly List<int> mappings;
private readonly int bytesPerSample;
/// <summary>
/// Creates a multiplexing wave provider, allowing re-patching of input channels to different
/// output channels
/// </summary>
/// <param name="inputs">Input wave providers. Must all be of the same format, but can have any number of channels</param>
/// <param name="numberOfOutputChannels">Desired number of output channels.</param>
public MultiplexingWaveProvider(IEnumerable<IWaveProvider> inputs, int numberOfOutputChannels)
{
this.inputs = new List<IWaveProvider>(inputs);
this.outputChannelCount = numberOfOutputChannels;
if (this.inputs.Count == 0)
{
throw new ArgumentException("You must provide at least one input");
}
if (numberOfOutputChannels < 1)
{
throw new ArgumentException("You must provide at least one output");
}
foreach (var input in this.inputs)
{
if (this.waveFormat == null)
{
if (input.WaveFormat.Encoding == WaveFormatEncoding.Pcm)
{
this.waveFormat = new WaveFormat(input.WaveFormat.SampleRate, input.WaveFormat.BitsPerSample, numberOfOutputChannels);
}
else if (input.WaveFormat.Encoding == WaveFormatEncoding.IeeeFloat)
{
this.waveFormat = WaveFormat.CreateIeeeFloatWaveFormat(input.WaveFormat.SampleRate, numberOfOutputChannels);
}
else
{
throw new ArgumentException("Only PCM and 32 bit float are supported");
}
}
else
{
if (input.WaveFormat.BitsPerSample != this.waveFormat.BitsPerSample)
{
throw new ArgumentException("All inputs must have the same bit depth");
}
if (input.WaveFormat.SampleRate != this.waveFormat.SampleRate)
{
throw new ArgumentException("All inputs must have the same sample rate");
}
}
inputChannelCount += input.WaveFormat.Channels;
}
this.bytesPerSample = this.waveFormat.BitsPerSample / 8;
mappings = new List<int>();
for (int n = 0; n < outputChannelCount; n++)
{
mappings.Add(n % inputChannelCount);
}
}
/// <summary>
/// persistent temporary buffer to prevent creating work for garbage collector
/// </summary>
private byte[] tempBuffer;
private byte[] GetBuffer(int size)
{
if (tempBuffer == null || tempBuffer.Length < size)
{
tempBuffer = new byte[Math.Max(size, this.WaveFormat.AverageBytesPerSecond)];
}
return tempBuffer;
}
/// <summary>
/// Reads data from this WaveProvider
/// </summary>
/// <param name="buffer">Buffer to be filled with sample data</param>
/// <param name="offset">Offset to write to within buffer, usually 0</param>
/// <param name="count">Number of bytes required</param>
/// <returns>Number of bytes read</returns>
public int Read(byte[] buffer, int offset, int count)
{
int outputBytesPerFrame = bytesPerSample * outputChannelCount;
int sampleFramesRequested = count / outputBytesPerFrame;
int inputOffset = 0;
int sampleFramesRead = 0;
// now we must read from all inputs, even if we don't need their data, so they stay in sync
foreach (var input in inputs)
{
int inputBytesPerFrame = bytesPerSample * input.WaveFormat.Channels;
int bytesRequired = sampleFramesRequested * inputBytesPerFrame;
byte[] inputBuffer = GetBuffer(bytesRequired);
int bytesRead = input.Read(inputBuffer, 0, bytesRequired);
sampleFramesRead = Math.Max(sampleFramesRead, bytesRead / inputBytesPerFrame);
for (int n = 0; n < input.WaveFormat.Channels; n++)
{
int inputIndex = inputOffset + n;
for (int outputIndex = 0; outputIndex < outputChannelCount; outputIndex++)
{
if (mappings[outputIndex] == inputIndex)
{
int inputBufferOffset = n * bytesPerSample;
int outputBufferOffset = offset + outputIndex * bytesPerSample;
int sample = 0;
while (sample < sampleFramesRequested && inputBufferOffset < bytesRead)
{
Array.Copy(inputBuffer, inputBufferOffset, buffer, outputBufferOffset, bytesPerSample);
outputBufferOffset += outputBytesPerFrame;
inputBufferOffset += inputBytesPerFrame;
sample++;
}
// clear the end
while (sample < sampleFramesRequested)
{
Array.Clear(buffer, outputBufferOffset, bytesPerSample);
outputBufferOffset += outputBytesPerFrame;
sample++;
}
}
}
}
inputOffset += input.WaveFormat.Channels;
}
return sampleFramesRead * outputBytesPerFrame;
}
/// <summary>
/// The WaveFormat of this WaveProvider
/// </summary>
public WaveFormat WaveFormat
{
get { return waveFormat; }
}
/// <summary>
/// Connects a specified input channel to an output channel
/// </summary>
/// <param name="inputChannel">Input Channel index (zero based). Must be less than InputChannelCount</param>
/// <param name="outputChannel">Output Channel index (zero based). Must be less than OutputChannelCount</param>
public void ConnectInputToOutput(int inputChannel, int outputChannel)
{
if (inputChannel < 0 || inputChannel >= InputChannelCount)
{
throw new ArgumentException("Invalid input channel");
}
if (outputChannel < 0 || outputChannel >= OutputChannelCount)
{
throw new ArgumentException("Invalid output channel");
}
mappings[outputChannel] = inputChannel;
}
/// <summary>
/// The number of input channels. Note that this is not the same as the number of input wave providers. If you pass in
/// one stereo and one mono input provider, the number of input channels is three.
/// </summary>
public int InputChannelCount
{
get { return inputChannelCount; }
}
/// <summary>
/// The number of output channels, as specified in the constructor.
/// </summary>
public int OutputChannelCount
{
get { return outputChannelCount; }
}
}
}

10
NAudioTests/NAudioTests.csproj

@ -23,7 +23,7 @@
<UpgradeBackupLocation>
</UpgradeBackupLocation>
<OldToolsVersion>3.5</OldToolsVersion>
<TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<PublishUrl>publish\</PublishUrl>
<Install>true</Install>
<InstallFrom>Disk</InstallFrom>
@ -39,6 +39,8 @@
<IsWebBootstrapper>false</IsWebBootstrapper>
<UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled>
<TargetFrameworkProfile>
</TargetFrameworkProfile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@ -60,6 +62,9 @@
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="Moq">
<HintPath>..\packages\Moq.4.0.10827\lib\NET35\Moq.dll</HintPath>
</Reference>
<Reference Include="nunit.framework, Version=2.5.10.11092, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
<HintPath>..\packages\NUnit.2.5.10.11092\lib\nunit.framework.dll</HintPath>
</Reference>
@ -90,6 +95,7 @@
<Compile Include="Mp3\Mp3FileReaderTests.cs" />
<Compile Include="Mp3\Mp3FrameTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="StopwatchExtensions.cs" />
<Compile Include="Utils\BlockAlignedWaveStream.cs" />
<Compile Include="Utils\NullWaveStream.cs" />
<Compile Include="Utils\OSUtils.cs" />
@ -100,6 +106,8 @@
<Compile Include="WaveStreams\BlockAlignmentReductionStreamTests.cs" />
<Compile Include="WaveStreams\CircularBufferTests.cs" />
<Compile Include="WaveStreams\MonoToStereoProvider16Tests.cs" />
<Compile Include="WaveStreams\MultiplexingSampleProviderTests.cs" />
<Compile Include="WaveStreams\MultiplexingWaveProviderTests.cs" />
<Compile Include="WaveStreams\StereoToMonoProvider16Tests.cs" />
<Compile Include="WaveStreams\WaveChannel32Tests.cs" />
<Compile Include="WaveStreams\WaveFileReaderTests.cs" />

29
NAudioTests/StopwatchExtensions.cs

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
namespace System.Diagnostics
{
public static class StopwatchExtensions
{
public static long Time(this Stopwatch sw, Action action, int iterations)
{
sw.Reset();
sw.Start();
for (int i = 0; i < iterations; i++)
{
action();
}
sw.Stop();
return sw.ElapsedMilliseconds;
}
public static long Time(this Stopwatch sw, Action action)
{
return Time(sw, action, 1);
}
}
}

276
NAudioTests/WaveStreams/MultiplexingSampleProviderTests.cs

@ -0,0 +1,276 @@
using System;
using System.Collections.Generic;
using System.Text;
using NUnit.Framework;
using NAudio.Wave.SampleProviders;
using NAudio.Wave;
using System.Diagnostics;
using Moq;
namespace NAudioTests.WaveStreams
{
[TestFixture]
public class MultiplexingSampleProviderTests
{
[Test]
public void NullInputsShouldThrowException()
{
Assert.Throws<ArgumentNullException>(() => new MultiplexingSampleProvider(null, 1));
}
[Test]
public void ZeroInputsShouldThrowException()
{
Assert.Throws<ArgumentException>(() => new MultiplexingSampleProvider(new ISampleProvider[] { }, 1));
}
[Test]
public void ZeroOutputsShouldThrowException()
{
var input1 = new Mock<ISampleProvider>();
Assert.Throws<ArgumentException>(() => new MultiplexingSampleProvider(new ISampleProvider[] { input1.Object }, 0));
}
[Test]
public void InvalidWaveFormatShouldThowException()
{
var input1 = new Mock<ISampleProvider>();
input1.Setup(x => x.WaveFormat).Returns(new WaveFormat(32000, 16, 1));
Assert.Throws<ArgumentException>(() => new MultiplexingSampleProvider(new ISampleProvider[] { input1.Object }, 1));
}
[Test]
public void OneInOneOutShouldCopyWaveFormat()
{
var input1 = new Mock<ISampleProvider>();
var inputWaveFormat = WaveFormat.CreateIeeeFloatWaveFormat(32000, 1);
input1.Setup(x => x.WaveFormat).Returns(inputWaveFormat);
var mp = new MultiplexingSampleProvider(new ISampleProvider[] { input1.Object }, 1);
Assert.AreEqual(inputWaveFormat, mp.WaveFormat);
}
[Test]
public void OneInTwoOutShouldCopyWaveFormatButBeStereo()
{
var input1 = new Mock<ISampleProvider>();
var inputWaveFormat = WaveFormat.CreateIeeeFloatWaveFormat(32000, 1);
input1.Setup(x => x.WaveFormat).Returns(inputWaveFormat);
var mp = new MultiplexingSampleProvider(new ISampleProvider[] { input1.Object }, 2);
var expectedOutputWaveFormat = WaveFormat.CreateIeeeFloatWaveFormat(32000, 2);
Assert.AreEqual(expectedOutputWaveFormat, mp.WaveFormat);
}
class TestSampleProvider : ISampleProvider
{
private int length;
public TestSampleProvider(int sampleRate, int channels, int lengthInBytes = Int32.MaxValue)
{
this.WaveFormat = WaveFormat.CreateIeeeFloatWaveFormat(sampleRate, channels);
this.length = lengthInBytes;
}
public int Read(float[] buffer, int offset, int count)
{
int n = 0;
while (n < count && Position < length)
{
buffer[n + offset] = Position;
n++; Position++;
}
return n;
}
public WaveFormat WaveFormat { get; set; }
public int Position { get; set; }
}
[Test]
public void OneInOneOutShouldCopyInReadMethod()
{
var input1 = new TestSampleProvider(32000, 1);
float[] expected = new float[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var mp = new MultiplexingSampleProvider(new ISampleProvider[] { input1 }, 1);
EnsureReadsExpected(mp, expected);
}
[Test]
public void OneInTwoOutShouldConvertMonoToStereo()
{
var input1 = new TestSampleProvider(32000, 1);
float[] expected = new float[] { 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9 };
var mp = new MultiplexingSampleProvider(new ISampleProvider[] { input1 }, 2);
EnsureReadsExpected(mp, expected);
}
private void EnsureReadsExpected(MultiplexingSampleProvider mp, float[] expected)
{
float[] buffer = new float[expected.Length];
var read = mp.Read(buffer, 0, expected.Length);
Assert.AreEqual(expected.Length, read);
Assert.AreEqual(expected, buffer);
}
[Test]
public void TwoInOneOutShouldSelectLeftChannel()
{
var input1 = new TestSampleProvider(32000, 2);
float[] expected = new float[] { 0, 2, 4, 6, 8, 10, 12, 14, 16, 18 };
var mp = new MultiplexingSampleProvider(new ISampleProvider[] { input1 }, 1);
EnsureReadsExpected(mp, expected);
}
[Test]
public void TwoInOneOutShouldCanBeConfiguredToSelectRightChannel()
{
var input1 = new TestSampleProvider(32000, 2);
float[] expected = new float[] { 1, 3, 5, 7, 9, 11, 13, 15, 17, 19 };
var mp = new MultiplexingSampleProvider(new ISampleProvider[] { input1 }, 1);
mp.ConnectInputToOutput(1, 0);
EnsureReadsExpected(mp, expected);
}
[Test]
public void StereoInTwoOutShouldCopyStereo()
{
var input1 = new TestSampleProvider(32000, 2);
float[] expected = new float[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 };
var mp = new MultiplexingSampleProvider(new ISampleProvider[] { input1 }, 2);
EnsureReadsExpected(mp, expected);
}
[Test]
public void TwoMonoInTwoOutShouldCreateStereo()
{
var input1 = new TestSampleProvider(32000, 1);
var input2 = new TestSampleProvider(32000, 1) { Position = 100 };
float[] expected = new float[] { 0, 100, 1, 101, 2, 102, 3, 103, 4, 104, 5, 105 };
var mp = new MultiplexingSampleProvider(new ISampleProvider[] { input1, input2 }, 2);
EnsureReadsExpected(mp, expected);
}
[Test]
public void StereoInTwoOutCanBeConfiguredToSwapLeftAndRight()
{
var input1 = new TestSampleProvider(32000, 2);
float[] expected = new float[] { 1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10 };
var mp = new MultiplexingSampleProvider(new ISampleProvider[] { input1 }, 2);
mp.ConnectInputToOutput(0, 1);
mp.ConnectInputToOutput(1, 0);
EnsureReadsExpected(mp, expected);
}
[Test]
public void HasConnectInputToOutputMethod()
{
var input1 = new TestSampleProvider(32000, 2);
var mp = new MultiplexingSampleProvider(new ISampleProvider[] { input1 }, 1);
mp.ConnectInputToOutput(1, 0);
}
[Test]
public void ConnectInputToOutputThrowsExceptionForInvalidInput()
{
var input1 = new TestSampleProvider(32000, 2);
var mp = new MultiplexingSampleProvider(new ISampleProvider[] { input1 }, 1);
Assert.Throws<ArgumentException>(() => mp.ConnectInputToOutput(2, 0));
}
[Test]
public void ConnectInputToOutputThrowsExceptionForInvalidOutput()
{
var input1 = new TestSampleProvider(32000, 2);
var mp = new MultiplexingSampleProvider(new ISampleProvider[] { input1 }, 1);
Assert.Throws<ArgumentException>(() => mp.ConnectInputToOutput(1, 1));
}
[Test]
public void InputChannelCountIsCorrect()
{
var input1 = new TestSampleProvider(32000, 2);
var input2 = new TestSampleProvider(32000, 1);
var mp = new MultiplexingSampleProvider(new ISampleProvider[] { input1, input2 }, 1);
Assert.AreEqual(3, mp.InputChannelCount);
}
[Test]
public void OutputChannelCountIsCorrect()
{
var input1 = new TestSampleProvider(32000, 1);
var mp = new MultiplexingSampleProvider(new ISampleProvider[] { input1 }, 3);
Assert.AreEqual(3, mp.OutputChannelCount);
}
[Test]
public void ThrowsExceptionIfSampleRatesDiffer()
{
var input1 = new TestSampleProvider(32000, 2);
var input2 = new TestSampleProvider(44100, 1);
Assert.Throws<ArgumentException>(() => new MultiplexingSampleProvider(new ISampleProvider[] { input1, input2 }, 1));
}
[Test]
public void ReadReturnsZeroIfSingleInputHasReachedEnd()
{
var input1 = new TestSampleProvider(32000, 1, 0);
float[] expected = new float[] { };
var mp = new MultiplexingSampleProvider(new ISampleProvider[] { input1 }, 1);
float[] buffer = new float[10];
var read = mp.Read(buffer, 0, buffer.Length);
Assert.AreEqual(0, read);
}
[Test]
public void ReadReturnsCountIfOneInputHasEndedButTheOtherHasnt()
{
var input1 = new TestSampleProvider(32000, 1, 0);
var input2 = new TestSampleProvider(32000, 1);
float[] expected = new float[] { 0, 0, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7 };
var mp = new MultiplexingSampleProvider(new ISampleProvider[] { input1, input2 }, 2);
EnsureReadsExpected(mp, expected);
}
[Test]
public void ShouldZeroOutBufferIfInputStopsShort()
{
var input1 = new TestSampleProvider(32000, 1, 6);
float[] expected = new float[] { 0, 1, 2, 3, 4, 5, 0, 0, 0, 0 };
var mp = new MultiplexingSampleProvider(new ISampleProvider[] { input1 }, 1);
float[] buffer = new float[10];
for (int n = 0; n < buffer.Length; n++)
{
buffer[n] = 99;
}
var read = mp.Read(buffer, 0, buffer.Length);
Assert.AreEqual(6, read);
Assert.AreEqual(expected, buffer);
}
public void PerformanceTest()
{
var input1 = new TestSampleProvider(32000, 1);
var input2 = new TestSampleProvider(32000, 1);
var input3 = new TestSampleProvider(32000, 1);
var input4 = new TestSampleProvider(32000, 1);
var mp = new MultiplexingSampleProvider(new ISampleProvider[] { input1, input2, input3, input4 }, 4);
mp.ConnectInputToOutput(0, 3);
mp.ConnectInputToOutput(1, 2);
mp.ConnectInputToOutput(2, 1);
mp.ConnectInputToOutput(3, 0);
float[] buffer = new float[input1.WaveFormat.AverageBytesPerSecond / 4];
Stopwatch s = new Stopwatch();
var duration = s.Time(() =>
{
// read one hour worth of audio
for (int n = 0; n < 60 * 60; n++)
{
mp.Read(buffer, 0, buffer.Length);
}
});
Console.WriteLine("Performance test took {0}ms", duration);
}
}
}

339
NAudioTests/WaveStreams/MultiplexingWaveProviderTests.cs

@ -0,0 +1,339 @@
using System;
using System.Collections.Generic;
using System.Text;
using NUnit.Framework;
using NAudio.Wave;
using System.Diagnostics;
using Moq;
namespace NAudioTests.WaveStreams
{
[TestFixture]
public class MultiplexingWaveProviderTests
{
[Test]
public void NullInputsShouldThrowException()
{
Assert.Throws<ArgumentNullException>(() => new MultiplexingWaveProvider(null, 1));
}
[Test]
public void ZeroInputsShouldThrowException()
{
Assert.Throws<ArgumentException>(() => new MultiplexingWaveProvider(new IWaveProvider[] { }, 1));
}
[Test]
public void ZeroOutputsShouldThrowException()
{
var input1 = new Mock<IWaveProvider>();
Assert.Throws<ArgumentException>(() => new MultiplexingWaveProvider(new IWaveProvider[] { input1.Object }, 0));
}
[Test]
public void InvalidWaveFormatShouldThowException()
{
var input1 = new Mock<IWaveProvider>();
input1.Setup(x => x.WaveFormat).Returns(new Gsm610WaveFormat());
Assert.Throws<ArgumentException>(() => new MultiplexingWaveProvider(new IWaveProvider[] { input1.Object }, 1));
}
[Test]
public void OneInOneOutShouldCopyWaveFormat()
{
var input1 = new Mock<IWaveProvider>();
var inputWaveFormat = new WaveFormat(32000, 16, 1);
input1.Setup(x => x.WaveFormat).Returns(inputWaveFormat);
var mp = new MultiplexingWaveProvider(new IWaveProvider[] { input1.Object }, 1);
Assert.AreEqual(inputWaveFormat, mp.WaveFormat);
}
[Test]
public void OneInTwoOutShouldCopyWaveFormatButBeStereo()
{
var input1 = new Mock<IWaveProvider>();
var inputWaveFormat = new WaveFormat(32000, 16, 1);
input1.Setup(x => x.WaveFormat).Returns(inputWaveFormat);
var mp = new MultiplexingWaveProvider(new IWaveProvider[] { input1.Object }, 2);
var expectedOutputWaveFormat = new WaveFormat(32000, 16, 2);
Assert.AreEqual(expectedOutputWaveFormat, mp.WaveFormat);
}
class TestWaveProvider : IWaveProvider
{
private int length;
public TestWaveProvider(WaveFormat format, int lengthInBytes = Int32.MaxValue)
{
this.WaveFormat = format;
this.length = lengthInBytes;
}
public int Read(byte[] buffer, int offset, int count)
{
int n = 0;
while (n < count && Position < length)
{
buffer[n + offset] = (byte)Position;
n++; Position++;
}
return n;
}
public WaveFormat WaveFormat { get; set; }
public int Position { get; set; }
}
[Test]
public void OneInOneOutShouldCopyInReadMethod()
{
var input1 = new TestWaveProvider(new WaveFormat(32000, 16, 1));
byte[] expected = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var mp = new MultiplexingWaveProvider(new IWaveProvider[] { input1 }, 1);
byte[] buffer = new byte[10];
var read = mp.Read(buffer, 0, 10);
Assert.AreEqual(10, read);
Assert.AreEqual(expected, buffer);
}
[Test]
public void OneInTwoOutShouldConvertMonoToStereo()
{
var input1 = new TestWaveProvider(new WaveFormat(32000, 16, 1));
// 16 bit so left right pairs
byte[] expected = new byte[] { 0, 1, 0, 1, 2, 3, 2, 3, 4, 5, 4, 5, 6, 7, 6, 7, 8, 9, 8, 9 };
var mp = new MultiplexingWaveProvider(new IWaveProvider[] { input1 }, 2);
byte[] buffer = new byte[20];
var read = mp.Read(buffer, 0, 20);
Assert.AreEqual(20, read);
Assert.AreEqual(expected, buffer);
}
[Test]
public void TwoInOneOutShouldSelectLeftChannel()
{
var input1 = new TestWaveProvider(new WaveFormat(32000, 16, 2));
// 16 bit so left right pairs
byte[] expected = new byte[] { 0, 1, 4, 5, 8, 9, 12, 13, 16, 17 };
var mp = new MultiplexingWaveProvider(new IWaveProvider[] { input1 }, 1);
byte[] buffer = new byte[10];
var read = mp.Read(buffer, 0, 10);
Assert.AreEqual(10, read);
Assert.AreEqual(expected, buffer);
}
[Test]
public void TwoInOneOutShouldCanBeConfiguredToSelectRightChannel()
{
var input1 = new TestWaveProvider(new WaveFormat(32000, 16, 2));
// 16 bit so left right pairs
byte[] expected = new byte[] { 2, 3, 6, 7, 10, 11, 14, 15, 18, 19 };
var mp = new MultiplexingWaveProvider(new IWaveProvider[] { input1 }, 1);
mp.ConnectInputToOutput(1, 0);
byte[] buffer = new byte[10];
var read = mp.Read(buffer, 0, 10);
Assert.AreEqual(10, read);
Assert.AreEqual(expected, buffer);
}
[Test]
public void StereoInTwoOutShouldCopyStereo()
{
var input1 = new TestWaveProvider(new WaveFormat(32000, 16, 2));
// 4 bytes per pair of samples
byte[] expected = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
var mp = new MultiplexingWaveProvider(new IWaveProvider[] { input1 }, 2);
byte[] buffer = new byte[12];
var read = mp.Read(buffer, 0, 12);
Assert.AreEqual(12, read);
Assert.AreEqual(expected, buffer);
}
[Test]
public void TwoMonoInTwoOutShouldCreateStereo()
{
var input1 = new TestWaveProvider(new WaveFormat(32000, 16, 1));
var input2 = new TestWaveProvider(new WaveFormat(32000, 16, 1)) { Position = 100 };
// 4 bytes per pair of samples
byte[] expected = new byte[] { 0, 1, 100, 101, 2, 3, 102, 103, 4, 5, 104, 105, };
var mp = new MultiplexingWaveProvider(new IWaveProvider[] { input1, input2 }, 2);
byte[] buffer = new byte[expected.Length];
var read = mp.Read(buffer, 0, expected.Length);
Assert.AreEqual(expected.Length, read);
Assert.AreEqual(expected, buffer);
}
[Test]
public void StereoInTwoOutCanBeConfiguredToSwapLeftAndRight()
{
var input1 = new TestWaveProvider(new WaveFormat(32000, 16, 2));
// 4 bytes per pair of samples
byte[] expected = new byte[] { 2, 3, 0, 1, 6, 7, 4, 5, 10, 11, 8, 9, };
var mp = new MultiplexingWaveProvider(new IWaveProvider[] { input1 }, 2);
mp.ConnectInputToOutput(0, 1);
mp.ConnectInputToOutput(1, 0);
byte[] buffer = new byte[12];
var read = mp.Read(buffer, 0, 12);
Assert.AreEqual(12, read);
Assert.AreEqual(expected, buffer);
}
[Test]
public void HasConnectInputToOutputMethod()
{
var input1 = new TestWaveProvider(new WaveFormat(32000, 16, 2));
var mp = new MultiplexingWaveProvider(new IWaveProvider[] { input1 }, 1);
mp.ConnectInputToOutput(1, 0);
}
[Test]
public void ConnectInputToOutputThrowsExceptionForInvalidInput()
{
var input1 = new TestWaveProvider(new WaveFormat(32000, 16, 2));
var mp = new MultiplexingWaveProvider(new IWaveProvider[] { input1 }, 1);
Assert.Throws<ArgumentException>(() => mp.ConnectInputToOutput(2, 0));
}
[Test]
public void ConnectInputToOutputThrowsExceptionForInvalidOutput()
{
var input1 = new TestWaveProvider(new WaveFormat(32000, 16, 2));
var mp = new MultiplexingWaveProvider(new IWaveProvider[] { input1 }, 1);
Assert.Throws<ArgumentException>(() => mp.ConnectInputToOutput(1, 1));
}
[Test]
public void InputChannelCountIsCorrect()
{
var input1 = new TestWaveProvider(new WaveFormat(32000, 16, 2));
var input2 = new TestWaveProvider(new WaveFormat(32000, 16, 1));
var mp = new MultiplexingWaveProvider(new IWaveProvider[] { input1, input2 }, 1);
Assert.AreEqual(3, mp.InputChannelCount);
}
[Test]
public void OutputChannelCountIsCorrect()
{
var input1 = new TestWaveProvider(new WaveFormat(32000, 16, 1));
var mp = new MultiplexingWaveProvider(new IWaveProvider[] { input1 }, 3);
Assert.AreEqual(3, mp.OutputChannelCount);
}
[Test]
public void ThrowsExceptionIfSampleRatesDiffer()
{
var input1 = new TestWaveProvider(new WaveFormat(32000, 16, 2));
var input2 = new TestWaveProvider(new WaveFormat(44100, 16, 1));
Assert.Throws<ArgumentException>(() => new MultiplexingWaveProvider(new IWaveProvider[] { input1, input2 }, 1));
}
[Test]
public void ThrowsExceptionIfBitDepthsDiffer()
{
var input1 = new TestWaveProvider(new WaveFormat(32000, 16, 2));
var input2 = new TestWaveProvider(new WaveFormat(32000, 24, 1));
Assert.Throws<ArgumentException>(() => new MultiplexingWaveProvider(new IWaveProvider[] { input1, input2 }, 1));
}
[Test]
public void ReadReturnsZeroIfSingleInputHasReachedEnd()
{
var input1 = new TestWaveProvider(new WaveFormat(32000, 16, 1), 0);
byte[] expected = new byte[] { };
var mp = new MultiplexingWaveProvider(new IWaveProvider[] { input1 }, 1);
byte[] buffer = new byte[10];
var read = mp.Read(buffer, 0, 10);
Assert.AreEqual(0, read);
}
[Test]
public void ReadReturnsCountIfOneInputHasEndedButTheOtherHasnt()
{
var input1 = new TestWaveProvider(new WaveFormat(32000, 16, 1), 0);
var input2 = new TestWaveProvider(new WaveFormat(32000, 16, 1));
byte[] expected = new byte[] { };
var mp = new MultiplexingWaveProvider(new IWaveProvider[] { input1, input2 }, 1);
byte[] buffer = new byte[10];
var read = mp.Read(buffer, 0, 10);
Assert.AreEqual(10, read);
}
[Test]
public void ShouldZeroOutBufferIfInputStopsShort()
{
var input1 = new TestWaveProvider(new WaveFormat(32000, 16, 1), 6);
byte[] expected = new byte[] { 0, 1, 2, 3, 4, 5, 0, 0, 0, 0 };
var mp = new MultiplexingWaveProvider(new IWaveProvider[] { input1 }, 1);
byte[] buffer = new byte[10];
for (int n = 0; n < buffer.Length; n++)
{
buffer[n] = 0xFF;
}
var read = mp.Read(buffer, 0, buffer.Length);
Assert.AreEqual(6, read);
Assert.AreEqual(expected, buffer);
}
[Test]
public void CorrectlyHandles24BitAudio()
{
var input1 = new TestWaveProvider(new WaveFormat(32000, 24, 1));
byte[] expected = new byte[] { 0, 1, 2, 0, 1, 2, 3, 4, 5, 3, 4, 5, 6, 7, 8, 6, 7, 8, 9, 10, 11, 9, 10, 11 };
var mp = new MultiplexingWaveProvider(new IWaveProvider[] { input1 }, 2);
byte[] buffer = new byte[expected.Length];
var read = mp.Read(buffer, 0, expected.Length);
Assert.AreEqual(expected.Length, read);
Assert.AreEqual(expected, buffer);
}
[Test]
public void CorrectlyHandlesIeeeFloat()
{
var input1 = new TestWaveProvider(WaveFormat.CreateIeeeFloatWaveFormat(32000, 1));
byte[] expected = new byte[] { 0, 1, 2, 3, 0, 1, 2, 3, 4, 5, 6, 7, 4, 5, 6, 7, 8, 9, 10, 11, 8, 9, 10, 11, };
var mp = new MultiplexingWaveProvider(new IWaveProvider[] { input1 }, 2);
byte[] buffer = new byte[expected.Length];
var read = mp.Read(buffer, 0, expected.Length);
Assert.AreEqual(expected.Length, read);
Assert.AreEqual(expected, buffer);
}
[Test]
public void CorrectOutputFormatIsSetForIeeeFloat()
{
var input1 = new TestWaveProvider(WaveFormat.CreateIeeeFloatWaveFormat(32000, 1));
byte[] expected = new byte[] { 0, 1, 2, 3, 0, 1, 2, 3, 4, 5, 6, 7, 4, 5, 6, 7, 8, 9, 10, 11, 8, 9, 10, 11, };
var mp = new MultiplexingWaveProvider(new IWaveProvider[] { input1 }, 2);
Assert.AreEqual(WaveFormatEncoding.IeeeFloat, mp.WaveFormat.Encoding);
}
public void PerformanceTest()
{
var waveFormat = new WaveFormat(32000, 16, 1);
var input1 = new TestWaveProvider(waveFormat);
var input2 = new TestWaveProvider(waveFormat);
var input3 = new TestWaveProvider(waveFormat);
var input4 = new TestWaveProvider(waveFormat);
var mp = new MultiplexingWaveProvider(new IWaveProvider[] { input1, input2, input3, input4 }, 4);
mp.ConnectInputToOutput(0, 3);
mp.ConnectInputToOutput(1, 2);
mp.ConnectInputToOutput(2, 1);
mp.ConnectInputToOutput(3, 0);
byte[] buffer = new byte[waveFormat.AverageBytesPerSecond];
Stopwatch s = new Stopwatch();
var duration = s.Time(() =>
{
// read one hour worth of audio
for (int n = 0; n < 60 * 60; n++)
{
mp.Read(buffer, 0, buffer.Length);
}
});
Console.WriteLine("Performance test took {0}ms", duration);
}
}
}

1
NAudioTests/packages.config

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Moq" version="4.0.10827" />
<package id="NUnit" version="2.5.10.11092" />
</packages>
Loading…
Cancel
Save