Skip to content

Commit

Permalink
WaveFormatConversionProvider supports passing an IWaveProvider throug…
Browse files Browse the repository at this point in the history
…h an ACM codec. WaveFormatConversionStream uses it. Refactored ACM conversion to remove need for repositioning backwards in stream.
  • Loading branch information
markheath committed Oct 8, 2015
1 parent 1e10d67 commit 85b68dd
Show file tree
Hide file tree
Showing 6 changed files with 277 additions and 187 deletions.
1 change: 1 addition & 0 deletions NAudio/NAudio.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,7 @@
<Compile Include="Wave\WaveStreams\WaveChannel32.cs" />
<Compile Include="Wave\SampleProviders\SampleChannel.cs" />
<Compile Include="Wave\WaveStreams\WaveFileReader.cs" />
<Compile Include="Wave\WaveStreams\WaveFormatConversionProvider.cs" />
<Compile Include="Wave\WaveStreams\WaveFormatConversionStream.cs" />
<Compile Include="Wave\WaveStreams\WaveInBuffer.cs" />
<Compile Include="Wave\WaveStreams\WaveMixerStream32.cs" />
Expand Down
23 changes: 11 additions & 12 deletions NAudio/Wave/WaveFormats/Gsm610WaveFormat.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.IO;

// ReSharper disable once CheckNamespace
namespace NAudio.Wave
{
/// <summary>
Expand All @@ -12,29 +11,29 @@ namespace NAudio.Wave
[StructLayout(LayoutKind.Sequential, Pack = 2)]
public class Gsm610WaveFormat : WaveFormat
{
private short samplesPerBlock;
private readonly short samplesPerBlock;

/// <summary>
/// Creates a GSM 610 WaveFormat
/// For now hardcoded to 13kbps
/// </summary>
public Gsm610WaveFormat()
{
this.waveFormatTag = WaveFormatEncoding.Gsm610;
this.channels = 1;
this.averageBytesPerSecond = 1625;
this.bitsPerSample = 0; // must be zero
this.blockAlign = 65;
this.sampleRate = 8000;
waveFormatTag = WaveFormatEncoding.Gsm610;
channels = 1;
averageBytesPerSecond = 1625;
bitsPerSample = 0; // must be zero
blockAlign = 65;
sampleRate = 8000;

this.extraSize = 2;
this.samplesPerBlock = 320;
extraSize = 2;
samplesPerBlock = 320;
}

/// <summary>
/// Samples per block
/// </summary>
public short SamplesPerBlock { get { return this.samplesPerBlock; } }
public short SamplesPerBlock { get { return samplesPerBlock; } }

/// <summary>
/// Writes this structure to a BinaryWriter
Expand Down
179 changes: 179 additions & 0 deletions NAudio/Wave/WaveStreams/WaveFormatConversionProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
using System;
using System.Diagnostics;
using NAudio.Wave.Compression;

// ReSharper disable once CheckNamespace
namespace NAudio.Wave
{
/// <summary>
/// IWaveProvider that passes through an ACM Codec
/// </summary>
public class WaveFormatConversionProvider : IWaveProvider, IDisposable
{
private readonly AcmStream conversionStream;
private readonly IWaveProvider sourceProvider;
private readonly WaveFormat targetFormat;
private readonly int preferredSourceReadSize;
private int leftoverDestBytes;
private int leftoverDestOffset;
private int leftoverSourceBytes;
private bool isDisposed;
/// <summary>
/// Create a new WaveFormat conversion stream
/// </summary>
/// <param name="targetFormat">Desired output format</param>
/// <param name="sourceProvider">Source Provider</param>
public WaveFormatConversionProvider(WaveFormat targetFormat, IWaveProvider sourceProvider)
{
this.sourceProvider = sourceProvider;
this.targetFormat = targetFormat;

conversionStream = new AcmStream(sourceProvider.WaveFormat, targetFormat);

preferredSourceReadSize = Math.Min(sourceProvider.WaveFormat.AverageBytesPerSecond, conversionStream.SourceBuffer.Length);
preferredSourceReadSize -= (preferredSourceReadSize% sourceProvider.WaveFormat.BlockAlign);
}

/// <summary>
/// Gets the WaveFormat of this stream
/// </summary>
public WaveFormat WaveFormat
{
get
{
return targetFormat;
}
}

/// <summary>
/// Indicates that a reposition has taken place, and internal buffers should be reset
/// </summary>
public void Reposition()
{
leftoverDestBytes = 0;
leftoverDestOffset = 0;
leftoverSourceBytes = 0;
conversionStream.Reposition();
}

/// <summary>
/// Reads bytes from this stream
/// </summary>
/// <param name="buffer">Buffer to read into</param>
/// <param name="offset">Offset in buffer to read into</param>
/// <param name="count">Number of bytes to read</param>
/// <returns>Number of bytes read</returns>
public int Read(byte[] buffer, int offset, int count)
{
int bytesRead = 0;
if (count % WaveFormat.BlockAlign != 0)
{
//throw new ArgumentException("Must read complete blocks");
count -= (count % WaveFormat.BlockAlign);
}

while (bytesRead < count)
{
// first copy in any leftover destination bytes
int readFromLeftoverDest = Math.Min(count - bytesRead, leftoverDestBytes);
if (readFromLeftoverDest > 0)
{
Array.Copy(conversionStream.DestBuffer, leftoverDestOffset, buffer, offset+bytesRead, readFromLeftoverDest);
leftoverDestOffset += readFromLeftoverDest;
leftoverDestBytes -= readFromLeftoverDest;
bytesRead += readFromLeftoverDest;
}
if (bytesRead >= count)
{
// we've fulfilled the request from the leftovers alone
break;
}

// now we'll convert one full source buffer
var sourceReadSize = Math.Min(preferredSourceReadSize,
conversionStream.SourceBuffer.Length - leftoverSourceBytes);

// always read our preferred size, we can always keep leftovers for the next call to Read if we get
// too much
int sourceBytesRead = sourceProvider.Read(conversionStream.SourceBuffer, leftoverSourceBytes, sourceReadSize);
int sourceBytesAvailable = sourceBytesRead + leftoverSourceBytes;
if (sourceBytesAvailable == 0)
{
// we've reached the end of the input
break;
}

int sourceBytesConverted;
int destBytesConverted = conversionStream.Convert(sourceBytesAvailable, out sourceBytesConverted);
if (sourceBytesConverted == 0)
{
Debug.WriteLine(String.Format("Warning: couldn't convert anything from {0}", sourceBytesAvailable));
// no point backing up in this case as we're not going to manage to finish playing this
break;
}
leftoverSourceBytes = sourceBytesAvailable - sourceBytesConverted;

if (leftoverSourceBytes > 0)
{
// buffer.blockcopy is safe for overlapping copies
Buffer.BlockCopy(conversionStream.SourceBuffer, sourceBytesConverted, conversionStream.SourceBuffer,
0, leftoverSourceBytes);
}

if (destBytesConverted > 0)
{
int bytesRequired = count - bytesRead;
int toCopy = Math.Min(destBytesConverted, bytesRequired);

// save leftovers
if (toCopy < destBytesConverted)
{
leftoverDestBytes = destBytesConverted - toCopy;
leftoverDestOffset = toCopy;
}
Array.Copy(conversionStream.DestBuffer, 0, buffer, bytesRead + offset, toCopy);
bytesRead += toCopy;
}
else
{
// possible error here
Debug.WriteLine(string.Format("sourceBytesRead: {0}, sourceBytesConverted {1}, destBytesConverted {2}",
sourceBytesRead, sourceBytesConverted, destBytesConverted));
//Debug.Assert(false, "conversion stream returned nothing at all");
break;
}
}
return bytesRead;
}

/// <summary>
/// Disposes this stream
/// </summary>
/// <param name="disposing">true if the user called this</param>
protected virtual void Dispose(bool disposing)
{
if (!isDisposed)
{
isDisposed = true;
conversionStream.Dispose();
}
}

/// <summary>
/// Disposes this resource
/// </summary>
public void Dispose()
{
GC.SuppressFinalize(this);
Dispose(true);
}

/// <summary>
/// Finalizer
/// </summary>
~WaveFormatConversionProvider()
{
Dispose(false);
}
}
}
Loading

0 comments on commit 85b68dd

Please sign in to comment.