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

Playing WMA from a stream

Jan 25, 2011 at 9:30 PM

Hi,

Is it possible to play a WMA audio source from a stream with NAudio? I seem to be able to do this with WAV and MP3 using WaveFileReader(stream) and MP3FileReader(stream) respectively. However, there seems to be no equivalent for WMA.

Regards

Colin

Jan 26, 2011 at 1:19 AM

Hi Colin,

Actually it is possible - there is a new class (added about a month ago) named WMAFileReader

This is how I use it in Practice# and it works fine:

            else if (fileExt == WMAExtension)
            {
                m_waveReader = new WMAFileReader(filename);
                if (m_waveReader.WaveFormat.Encoding != WaveFormatEncoding.Pcm)
                {
                    m_waveReader = WaveFormatConversionStream.CreatePcmStream(m_waveReader);
                    m_waveReader = new BlockAlignReductionStream(m_waveReader);
                }
                if (m_waveReader.WaveFormat.BitsPerSample != 16)
                {
                    var format = new WaveFormat(m_waveReader.WaveFormat.SampleRate,
                       16, m_waveReader.WaveFormat.Channels);
                    m_waveReader = new WaveFormatConversionStream(format, m_waveReader);
                }

                m_waveChannel = new WaveChannel32(m_waveReader);
            }

Hope this helps,
Yuval
Author of Practice# - http://code.google.com/p/practicesharp

Jan 26, 2011 at 9:19 AM

Thanks for your help.

I have followed the discussion threads and I am aware that you implemented a WMAFileReader and that Mark eventually incorporated this in NAudio. Still, I cannot find it in the NAudio 1.3 assembly!

I also looked at your original implementation and unfortunately I cannot use it as it is as I need to stream the WMA file from memory rather than directly from disk. To this purpose, the Mp3 and Wav file readers have a constructor that accepts an object of type Stream, so I can pass a MemoryStream containing the audio resource. Actually.. I could do a bit of a hack by saving the stream to a temporary file and supply its URL for playback, but I would rather not do this as I would need to keep track of the temp files and delete them eventually.

 

Jan 26, 2011 at 11:48 AM

No problem - glad to help.

Mark put WMAFileReader in NAudio.WindowsMediaFormat assembly, not in the core NAudio assembly.

Also, I'm not sure if the compiled versions have it, but if you get the latest sources from SVN, then the file is there.

 

Regd. Memory Streams - Yes, I see your point. WMAFileReader is missing the stream constructor.

Well, this 'hack' your purposed is not the most elegant one, but it will save you coding time.

If you want to solve it properly, then you could add a constructor for streams (just like WAV and MP3 File Readers have one).

WMA playback is done by using the Microsoft Windows Media Format API, in particular IWMSyncReader:

http://msdn.microsoft.com/en-us/library/dd798576(v=vs.85).aspx 

The current implementation of WMAFileReader uses Open() method, but there is another method there named OpenStream():

http://msdn.microsoft.com/en-us/library/dd798598(v=vs.85).aspx

"The OpenStream method opens a stream for reading."

I'm not saying it's trivial, but if you provide your .NET memory stream as a COM IStream object, then WMA playback should work theoretically, with some other minor modifications to WMAFileReader.

 

HTH,

Yuval

Jan 26, 2011 at 1:59 PM

I'd rather not try to customize the NAudio code for this as I would have to redo the changes on every release of the library. The temp file hack is beginning to look more viable! :)

I can eventually eliminate the hack, if and when, a Stream-based constructor is implemented for WMAFileStream. 

 

Thanks once again.

Jan 26, 2011 at 2:43 PM

Collin,

I have no time to implement it, but just to finish my last reply.
This is the Microsoft IStream interface definition:

http://msdn.microsoft.com/en-us/library/aa380034(v=vs.85).aspx

There is a wrapper in .NET for this interface:

http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.comtypes.istream(v=VS.80).aspx

 And here's an article I found about .NET and IStream implementation (you will need a new class - e.g. WMAStreamWrapper):

http://hl7connect.blogspot.com/2010/04/c-implementation-of-istream.html

 

I'm sure it is doable.

 

BTW: there is one more option.. don't use WMA (COM) and move to using FLAC which is open source and better quality (though bigger in size).

It too has support for streaming. 

Jan 26, 2011 at 2:45 PM
colinvella wrote:

I'd rather not try to customize the NAudio code for this as I would have to redo the changes on every release of the library. The temp file hack is beginning to look more viable! :)

I can eventually eliminate the hack, if and when, a Stream-based constructor is implemented for WMAFileStream. 

 

Thanks once again.

You don't have to redo the changes - you do it once, send it to Mark, he would take the changes in, and you just contributed to NAudio and the open source community!

Yuval

Jan 26, 2011 at 2:52 PM

Thanks for all the pointers, I'll consider an implementation, although unfortunately my time is rather limited.

Regarding the audio format, for my application I am required to support only WAV, MP3 and WMA for the time being.

 

Jan 26, 2011 at 10:40 PM

I have tried implementing a Stream-based WmaFileReader as follows:

  • Implemented class ComStreamWrapper to wrap a Stream within an IStream
  • Implemented class WmaStream2 by cloning WmaStream and adding a constructor accepting a Stream. Internally, the Stream parameter is wrapped in ComStreamWrapper and passed to IWMSyncReader.OpenStream()
  • Implemented class WmaFileReader2 by cloning WmaFileReader, replacing refs to WmaStream by WmaStream2 and added constructors that accept Stream params. Internally they are identical to the filename ctors except that the internal WmaStream2 instance is constructed by passing the Stream parameter.

The new stream implementation seems to work up till IWavePlayer.Init() and the properties such as TotalTime, wave format etc. within WmaFileReader2 seem to make sense. However the code fails somewhere deep within IWavePlayer.Play() (I'm using WaveOut implementation). The error is a null reference expetion and it seems to point to a method GetNextSample() but I couldn't follow further.

I have attached the sources of the new classes above: http://www.mediafire.com/file/ds4b5v69tc6c33j/Audio.zip hoping that you may perhaps assist.

Assuming the error can be resolved, the intention of classes WmaStream2 and WmaFileReader2 is to replace the original WmaStream and WmaFileReader classes since they essentially just add constructors to accept Stream params.

 

Jan 26, 2011 at 11:14 PM

I'll try to run these classes in my NAudioWMA Test application.

But it could take some time to debug this 'adapter'.

Yuval

Jan 26, 2011 at 11:46 PM

I managed to play a WMA Memory Stream - simply read a WMA file as bytes into a MemoryStream.

 

		MemoryStream memoryStream = new MemoryStream();
                using ( FileStream infile = new FileStream(fileName, FileMode.Open, FileAccess.Read) )
                {
                    while (infile.Position < infile.Length)
                    {
                        byte data = (byte) infile.ReadByte();
                        memoryStream.WriteByte( data );
                    }
                }
                memoryStream.Position = 0;
                readerStream = new WMAFileReader2(memoryStream);

 

 

It crashed on ComStreamWrapper.Seek:

 

  public void Seek(long dlibMove, int dwOrigin, System.IntPtr plibNewPosition)
    {
        int newPosition = (int)m_stream.Seek(dlibMove, (SeekOrigin)dwOrigin);

        if (plibNewPosition != IntPtr.Zero)
        {
            Marshal.WriteInt32(plibNewPosition, newPosition);
        }
    }

 

I don't  know why, yet, but the plibNewPosition pointer came in as NULL (Zero). But working with COM in the past that wouldn't be a surprise or the first time..

So I added a quick protection check - if it comes as NULL I simply don't write the new position back.

The audio plays very well..!

 

There is a problem however with Seeks - My test application crashed (in the same Seek() method) when trying to see to a constant CurrentTime (it works fine with a file input):

            (mainOutputStream as WaveChannel32).CurrentTime = new TimeSpan(0, 0, 20);

Haven't had much time to debug it, but it seems that the conversion of Time Span to memory stream position is incorrect. The dlibMove parameter comes in with a huge value, which is much bigger than the memory stream length.

So I leave this to you for now..

 

Yuval


 

Jan 27, 2011 at 8:00 AM

Yuval, you're a star! :)

I'll amend my own copy with your null test. Hopefully Mark can incorporate the Stream constructors in the original WmaFileReader and WmaStream classes.

 

Regarding using the CurrentTime property's setter, I'm not 100% sure, but I think it is indeed a bug. The implementation of this setter essentially converts the TimeSpan input from continuous time to discreet sample time and calls the Position property's setter with the computed sample index. However, apparently this value must be rounded to the nearest BlockAlign value. In fact within my own application I had to bypass the CurrentTime property setter by implementing my own continuous time - discrete sample time conversion outside of NAudio (with BlockAlign rounding) and pass the value directly to the Position property setter.  Here's my application code below:

 

// skip to different times via trackbar control
private void OnTrackBarTimeScroll(object objSender, EventArgs eventArgs)
{
    // compute position in sample domain
    long nNewPosition = (m_trackBarTime.Value * m_waveStream.Length) / m_trackBarTime.Maximum;

    // align new pos to BlockAlign and assign to WaveStream Position
    nNewPosition -= nNewPosition % m_waveStream.BlockAlign;
    m_waveStream.Position = nNewPosition;

    // display new CurrentTime value
    m_lblCurrentTime.Text = m_waveStream.CurrentTime.ToString(@"hh\:mm\:ss");
}

 

Jan 27, 2011 at 11:58 AM

On a somewhat related note there seems to be a problem with the TotalTime property of the WmaFileStream. It seems to be returning a longer time by a factor of more than 100% (a 3-second WMA file is reported as being about 7 seconds long). The CurrentTime property on the other hand appears far more accurate.

Jan 27, 2011 at 12:16 PM

TotalTime works fine for me with files and memory streams, please take a look at a test application I wrote for WMA (before the code was submitted to Mark):

http://code.google.com/p/practicesharp/source/browse/#svn%2Ftrunk%2FTests%2FNAudioWMA

Jan 27, 2011 at 12:39 PM

The problem could be with the WMA file in question. For example, this file (http://www.mediafire.com/file/a8a4tafp71x809e/Ringtone%2001.wma) plays a short melody twice when I try it in Windows Media Player (duration about 7 seconds) but with NAudio it only plays "once" for about half the time, during which, the Position property of the underlying Stream (the wrapped within WaveStream) spans from beginning till end (Stream.Length). In Windows Media Player on the other hand, it plays the sound "twice", reflecting the TotalTime value reported by WmaFileStream. I am beginning to wonder if the WMA format supports some form of looping or repetition meta-data. 

Jan 27, 2011 at 1:23 PM

I downloaded the file and played it both in my Test application (mentioned above) and Practice# - Both played the melody twice for 7 seconds...

Not sure what the problem is.

It could be your application.

Jan 27, 2011 at 1:52 PM
Edited Jan 27, 2011 at 2:32 PM

I think I found what the problem is, although I'm still not close to a proper solution:

In my playback interface, I wanted to implement a means of detecting when the sound terminates (to enable/disable Play/Pause/Stop buttons and so on).

Attempt 1: I first tried binding to the IWavePlayer's PlaybackStopped event, but this is not working properly and the event is never triggered.

Attempt 2: Next, I set up a timer to poll for the IWavePlayer's PlayBackState property. Again, this property is not updated correctly for all media types and in many cases it never switches from "Playing" to "Stopped".

Attempt 3: I finally decided to poll the underlying Stream's (and later, WaveStream's) Position and trigger the end of play when the stream's position reaches it's Length (within a tollerance based on BlockAlign as it does not always reach the very end in some cases).

The last approach seemed to work reasonably well, and I could also map the stream's position to that of a an interactive playback slider control (the slider sometimes jumps to an initial offset due to the audio file's header size in the stream). However, when I tried playing Ringtone0.wma, this approach fails because the file seems to have an instruction to repeat the audio stream twice, thus my UI terminates playback prematurely as soon as the stream's position reaches the end of the stream. In hind-sight, I should probably base my checks on CurrentTime and ElapsedTime.

 I'll keep you posted :)

 

EDIT: Using CurrentTime and ElapsedTime solved the problem.. but only up to an extent - some audio files never quite reach the end of the song length, sometimes by a span of 2 or 3 seconds! I kind of solved this by keeping track of successive values of CurrentTime from one Timer tick to the next, and if this becomes constant, I assume that the end has been reached. This is working well so far.

There is only one pending problem, at least as far as my app is concerned: it is not possible to change the WaveStream position (even with BlockAlign rounding) in WmaFileReader2 as it causes a crash within the Seek method of WmaStream2, particularly during th invocation of the line:

m_reader.SetRange(SampleTime, 0);

 

 

Jan 27, 2011 at 7:57 PM

The solution has to be event driven - any kind of polling is really not the right way to do it due to latency issues.

 

As for the Seek error - I know, that is exactly what I described in a previous reply.

 

Jan 27, 2011 at 10:22 PM

Regarding the PlaybackStopped event not working.. I just found out what the problem was thanks to a recorded issue (http://naudio.codeplex.com/workitem/10726). Prior to playing, I was wrapping the wave stream in a WaveChannel32 that, by defaut, has property PadWithZeros set to true. This causes the stream to yield zeros ad infinitum and never trigger the PlaybackStopped  event. The solution was to set PadWithZeros to false. I have now switched back to the event system and just use polling to track playback progress over time.