This project has moved. For the latest updates, please go here.

Can't decode stereo mu law

Jun 5, 2011 at 2:19 PM

Here is some code that works, but not the way I want it to:

WaveFormat waveFormat = WaveFormat.CreateMuLawFormat(Sample.IsStereo ? Sample.Hertz * 2 : Sample.Hertz, 1); // can't decode stereo mu law???

var reader = new EgoWaveStream(EncodedTrack, waveFormat, Sample.StartBytes, Sample.EndBytes);

DecodedTrack = WaveFormatConversionStream.CreatePcmStream(reader);
I'm having double the rate because if I try to play the file in stereo an "AcmNotPossible calling acmFormatSuggest" exception is raised in Wave.Compression.AcmStream.SuggestPcmFormat().

The only unusual aspect of this is EgoWaveStream, which is a simple override of WaveStream that doesn't look for a file header and only reads the file between StartByte and EndByte. It does not cause any problems when I'm playing back PCM however, so I doubt it's an issue with mu law files either.

Is stereo mu-law conversion possible? The files *are* stereo; I can play them back just fine in other programs.

Coordinator
Jun 6, 2011 at 9:40 AM

If the built-in ACM doesn't support stereo a-law (which is what AcmNotPossible means), then you either need to stick with the trick you are doing now (which will work fine since mu-law decoding is stateless) or write a splitter stream to make two mono streams and a combiner stream afterwards. Alternatively, in the next NAudio there is a fully managed mu-law codec, so you could build your code around that if you prefer (look in the latest NAudio source code)

Mark

Jun 6, 2011 at 11:48 PM

Thanks, the new decoder does the trick.

Here is my code (from my WaveStream inheritor) for future readers:

 

public override int Read(byte[] buffer, int offset, int count)
{
	int TotalBytesRead = 0;

	byte[] DecodedBuffer = buffer;
	if (IsMuLaw)
	{
		count /= 2;
		buffer = new byte[count];
	}

	// read compressed data

	if (IsMuLaw)
	{
		for (int i = 0; i < TotalBytesRead; i++)
		{
			byte[] Decoded = BitConverter.GetBytes(NAudio.Codecs.MuLawDecoder.MuLawToLinearSample(buffer[i]));
			for (int j = 0; j < 2; j++)
				DecodedBuffer.SetValue(Decoded[j], (i*2) + j);
		}
		buffer = DecodedBuffer;
		TotalBytesRead *= 2;
	}

	return TotalBytesRead;
}

It's not very extensible, but that's okay for me. Presumably the new decoder will replace ye olde ACM eventually?

 

Coordinator
Jun 8, 2011 at 10:30 PM

ACM decoding support will always stay in the product as it can do more than just mu-law. However, I might package up the A/MuLaw decoder into a WaveStream in the future for ease of use.

Mark

Jun 24, 2011 at 1:34 PM
Edited Jun 26, 2011 at 3:28 PM

The inevitable happened. Here's a properly-implemented MuLaw conversion stream. Unlike WaveConversionStream you don't need to specify a format, and both WaveStreams and WaveProviders are handled.

If you want to add this to NAudio Mark, it's yours. :-)

/// <summary>
/// Processes MuLaw files without any use of ACM (which can't handle stereo).
/// </summary>
public class MuLawConversionStream : WaveStream
{
	IWaveProvider input;
	WaveStream input_stream { get { return input as WaveStream; } } // For Length/Position
	WaveFormat output_format;
	byte[] internal_buffer;

	public override WaveFormat WaveFormat { get { return output_format; } }

	public MuLawConversionStream(IWaveProvider input)
	{
		if (input.WaveFormat.Encoding != WaveFormatEncoding.MuLaw && input.WaveFormat.Encoding != WaveFormatEncoding.Pcm)
			throw new InvalidDataException("Input stream must be MuLaw or PCM.");

		this.input = input;

		if (input.WaveFormat.Encoding == WaveFormatEncoding.MuLaw)
			output_format = new WaveFormat(input.WaveFormat.SampleRate, input.WaveFormat.Channels);
		else
			output_format = WaveFormat.CreateMuLawFormat(input.WaveFormat.SampleRate, input.WaveFormat.Channels);
	}

	public int Encode(byte[] input, int offset, int in_count, byte[] output)
	{
		if (output.Length < in_count / 2)
			throw new IndexOutOfRangeException(String.Format("Output buffer not large enough ({0} should be >= {1}).", output.Length, offset + (in_count/2) ));

		if (in_count % 2 != 0)
			throw new InvalidDataException("Got a half-sample from input source");

		for (int i = 0; i < in_count; i += 2) // Increment by 2: 16-bit data
			output[ offset + (i/2) ] = NAudio.Codecs.MuLawEncoder.LinearToMuLawSample(BitConverter.ToInt16(input, i));

		return in_count / 2;
	}

	public int Decode(byte[] input, int offset, int in_count, byte[] output)
	{
		if (output.Count() < in_count * 2)
			throw new IndexOutOfRangeException(String.Format("Output buffer not large enough ({0} should be >= {1}).", output.Length, offset + (in_count*2) ));

		for (int i = 0; i < in_count; i++)
		{
			byte[] DecodedShort = BitConverter.GetBytes(NAudio.Codecs.MuLawDecoder.MuLawToLinearSample(input[i]));
			for (int j = 0; j < 2; j++)
				output.SetValue(DecodedShort[j], offset + (i*2) + j);
		}

		return in_count * 2;
	}

	public override int Read(byte[] buffer, int offset, int count)
	{
		if (input.WaveFormat.Encoding == WaveFormatEncoding.MuLaw)
		{
			if (internal_buffer == null || internal_buffer.Length != count / 2)
				internal_buffer = new byte[count / 2];

			int bytes_read = input.Read(internal_buffer, 0, count / 2);
			return Decode(internal_buffer, offset, bytes_read, buffer);
		}
		else
		{
			if (internal_buffer == null || internal_buffer.Length != count * 2)
				internal_buffer = new byte[count * 2];

			int bytes_read = input.Read(internal_buffer, 0, count * 2);
			return Encode(internal_buffer, offset, bytes_read, buffer);
		}
	}

	protected override void Dispose(bool disposing)
	{
		if (input_stream != null)
			input_stream.Dispose();
		base.Dispose(disposing);
	}

	public override long Length
	{
		get
		{
			if (input_stream != null)
			{
				if (input.WaveFormat.Encoding == WaveFormatEncoding.MuLaw)
					return input_stream.Length * 2;
				else
					return input_stream.Length / 2;
			}
			else
				return 0;
		}
	}

	public override long Position
	{
		get
		{
			if (input_stream != null)
			{
				if (input.WaveFormat.Encoding == WaveFormatEncoding.MuLaw)
					return input_stream.Position * 2;
				else
					return input_stream.Position / 2;
			}
			else
				return 0;
		}
		set
		{
			if (input_stream != null)
			{
				if (input.WaveFormat.Encoding == WaveFormatEncoding.MuLaw)
					input_stream.Position = value / 2;
				else
					input_stream.Position = value * 2;
			}
			else
				throw new InvalidOperationException("Wave input does not support Position.");
		}
	}
}

Jun 26, 2011 at 5:43 AM

Here is example utilization if needed.

 using (var reader = new WaveFileReader(fileToProcess))
 using (var converter = new MuLawConversionStream(reader))
 {
       WaveFileWriter.CreateWaveFile(outputFile, converter);
 }
Coordinator
Jun 26, 2011 at 9:19 AM

thanks for providing this. There are a couple of minor issues that would need to be fixed before including with NAudio:

1. The 'offset' parameter of the Read method is not being observed. This usually doesn't matter because it is 0, but there are occasions when it can be non-zero.

2. The WaveFormat method should not create a new object each time, and the Read method should avoid creating new buffers each time as we try to create no new objects in Read to avoid giving the garbage collector any work to do during playback. I usually create the waveFormat in the constructor, and reuse buffers I create in the Read method if they are long enough (Read almost always gets called with the same value in count every time)

3. You should use the Length property to get the number of elements in an array, rather than Count() which is a LINQ extension method (NAudio is compatible back to .NET 2 so it doesn't use LINQ)

4. NAudio follows the naming convention where private variable names & parameter names are lower cased

thanks

Mark

Jun 26, 2011 at 11:52 AM

I've made your changes in the post above. Not 100% sure what you meant by not observing the offset, but I think I've got it now.

Coordinator
Jun 26, 2011 at 2:36 PM

thanks, there's still a couple of bugs with offset in there. Your internal_buffer doesn't need an offset, so reading into it can should use an offset of 0:

int bytes_read = input.Read(internal_buffer, 0, count / 2);

then, in your Encode and Decode methods, you must copy count samples. Your loops run for count-offset samples.

for (int i = 0; i < in_count; i++)

Offset is only used when writing into the buffer variable that was passed into the Read method, so in for example in the encode method:

output[offset + i / 2] = NAudio.Codecs.MuLawEncoder.LinearToMuLawSample(BitConverter.ToInt16(input, i));

Jun 26, 2011 at 3:30 PM

Seems I didn't understand the purpose of offset at all! Edited the post again.

Coordinator
Jul 1, 2011 at 10:22 PM

cool, looks good now.