mirror of https://github.com/naudio/NAudio.git

9 changed files with 1029 additions and 3 deletions
-
2NAudio/NAudio.csproj
-
4NAudio/Properties/AssemblyInfo.cs
-
180NAudio/Wave/SampleProviders/MultiplexingSampleProvider.cs
-
191NAudio/Wave/WaveProviders/MultiplexingWaveProvider.cs
-
10NAudioTests/NAudioTests.csproj
-
29NAudioTests/StopwatchExtensions.cs
-
276NAudioTests/WaveStreams/MultiplexingSampleProviderTests.cs
-
339NAudioTests/WaveStreams/MultiplexingWaveProviderTests.cs
-
1NAudioTests/packages.config
@ -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; } |
|||
} |
|||
} |
|||
} |
@ -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; } |
|||
} |
|||
} |
|||
} |
@ -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); |
|||
} |
|||
} |
|||
} |
@ -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); |
|||
} |
|||
} |
|||
} |
@ -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,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> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue