0
votes

My task is to convert wma audio stream to mp3 stream using NAudio and Lame. The below code is working fine with file name but I want it to be done with memory stream. I search in NAudio there is no method for reading wma audio stream. Is it possible with NAudio?

    public static byte[] ConvertWmaToMp3(uint bitrate = 128)
    {

        FileStream fs = new FileStream("..\\sample.wma", FileMode.Open, FileAccess.Read);            
        var ws = new NAudio.WindowsMediaFormat.WMAFileReader(fs.Name);                                    

        // Setup encoder configuration
        WaveLib.WaveFormat fmt = new WaveLib.WaveFormat(ws.WaveFormat.SampleRate, 16, ws.WaveFormat.Channels);
        Yeti.Lame.BE_CONFIG beconf = new Yeti.Lame.BE_CONFIG(fmt, bitrate);

        // Encode WAV to MP3
        int blen = ws.WaveFormat.AverageBytesPerSecond;
        byte[] buffer = new byte[blen];
        byte[] mp3data = null;

        using (MemoryStream mp3strm = new MemoryStream())
        using (Mp3Writer mp3wri = new Mp3Writer(mp3strm, fmt, beconf))
        {
            int rc;
            while ((rc = ws.Read(buffer, 0, blen)) > 0)
            {
                mp3wri.Write(buffer, 0, rc);
            }

            mp3data = mp3strm.ToArray();
        }

        return mp3data;                      

    }
2

2 Answers

0
votes

Currently the WMAFileReader class doesn't support reading data from a stream. The WMA APIs support reading WMA from an IStream so it is definitely possible.

If you want to implement streaming yourself you'll need to grab the source code for WmaFileReader and WmaStream from CodePlex, and use them as templates for your modified classes.

First thing you'll need is a wrapper class that provides a COM IStream interface to a .NET Stream. Here's a simple one:

public class InteropStream : IStream, IDisposable
{
    public readonly Stream intern;

    public InteropStream(Stream strm)
    {
        intern = strm;
    }

    ~InteropStream()
    {
        Dispose(true);
    }

    public void Dispose()
    {
        Dispose(false);
    }

    protected void Dispose(bool final)
    {
        if (final)
            intern.Dispose();
    }

    #region IStream Members
    public void Clone(out IStream ppstm)
    {
        ppstm = null;
    }

    public void Commit(int grfCommitFlags)
    {
        intern.Flush();
    }

    readonly byte[] buffer = new byte[4096];

    public void CopyTo(IStream pstm, long cb, IntPtr pcbRead, IntPtr pcbWritten)
    {
        if (pcbRead != IntPtr.Zero)
            Marshal.WriteInt32(pcbRead, 0);
        if (pcbWritten != IntPtr.Zero)
            Marshal.WriteInt32(pcbWritten, 0);
    }

    public void LockRegion(long libOffset, long cb, int dwLockType)
    { }

    public void Read(byte[] pv, int cb, IntPtr pcbRead)
    {
        int rc = intern.Read(pv, 0, cb);
        if (pcbRead != IntPtr.Zero)
            Marshal.WriteInt32(pcbRead, rc);
    }

    public void Revert()
    { }

    public void Seek(long dlibMove, int dwOrigin, IntPtr plibNewPosition)
    {
        long origin = 0;
        if (dwOrigin == 1) // STREAM_SEEK_CUR
            origin = intern.Position;
        else if (dwOrigin == 2) // STREAM_SEEK_END
            origin = intern.Length;

        long pos = origin + dlibMove;
        intern.Position = pos;

        if (plibNewPosition != IntPtr.Zero)
            Marshal.WriteInt64(plibNewPosition, pos);
    }

    public void SetSize(long libNewSize)
    { }

    public void Stat(out System.Runtime.InteropServices.ComTypes.STATSTG pstatstg, int grfStatFlag)
    {
        var res = new System.Runtime.InteropServices.ComTypes.STATSTG();

        res.type = 2; // STGTY_STREAM
        res.cbSize = intern.Length;

        pstatstg = res;
    }

    public void UnlockRegion(long libOffset, long cb, int dwLockType)
    { }

    public void Write(byte[] pv, int cb, IntPtr pcbWritten)
    { }
    #endregion
}

Next copy the WmaStream code to a new namespace in your project and add the following code to the top of the class:

    InteropStream interopStrm = null;

    public WmaStream(Stream fileStream)
        : this(fileStream, null)
    { }

    public WmaStream(Stream fileStream, WaveFormat OutputFormat)
    {
        interopStrm = new InteropStream(fileStream);
        m_reader = WM.CreateSyncReader(WMT_RIGHTS.WMT_RIGHT_NO_DRM);
        try
        {
            IWMSyncReader2 rdr = m_reader as IWMSyncReader2;
            rdr.OpenStream(interopStrm);
            Init(OutputFormat);
        }
        catch
        {
            try
            {
                m_reader.Close();
            }
            catch
            {
            }
            m_reader = null;
            throw;
        }
    }

Do the same with WmaFileReader and the following code:

    public WMAFileReader(Stream wmaStream)
    {
        m_wmaStream = new WmaStream2(wmaStream);
        m_waveFormat = m_wmaStream.Format;
    }

Now you can create an instance of your modified WmaFileReader using either a filename or a Stream instance - MemoryStream, FileStream, etc. The Stream instance needs to be readable and seekable.

I've tried the above on a few random WMA files I found on my computer, loaded into MemoryStream or using FileStream, and it works as I expected it to.

Presumably Mark is working on adding this functionality to the NAudio.Wma package, so consider this an interim fix until NAudio supports it.

0
votes

The easiest way to convert WMA to MP3 with NAudio is to use the Media Foundation based classes. If you are on Windows 8 and above, and MP3 encoder should be available.

using (var reader = new MediaFoundationReader("test.wma"))
{
    MediaFoundationEncoder.EncodeToMp3(reader, "test.mp3");
}

On systems without an MP3 encoder, I tend to use LAME.exe and stream the input audio through stdin. See my article here for more info on this approach.