This project has moved and is read-only. For the latest updates, please go here.

Coding Encoding in memory (NAudio 1.7 alpha)

Jul 29, 2013 at 11:16 PM
I was stuck with problem that I wanted to use coding and encoding in memory. I resolved the issue with MediaFoundationReader, which read the file from physical path and doesn't support this operation using memory stream. And what to share my approach

So I've extended this class as following:
internal class ExtendedMediaFounadtionReader : MediaFoundationReader
    {
        private static byte[] fileContent;
        private static string SetFileContentFromBytesArray(byte[] audioFile)
        {
            fileContent = audioFile;
            return null;
        }
        public ExtendedMediaFounadtionReader(string file) : base(file)
        {
        }

        public ExtendedMediaFounadtionReader(string file, MediaFoundationReaderSettings settings) : base(file, settings)
        {
        }

        public ExtendedMediaFounadtionReader(byte[] file)
            : base(SetFileContentFromBytesArray(file))
        {
        }

        public ExtendedMediaFounadtionReader(byte[] file, MediaFoundationReaderSettings settings)
            : base(SetFileContentFromBytesArray(file), settings)
        {
        }

        protected override IMFSourceReader CreateReader(MediaFoundationReaderSettings settings)
        {
            if (fileContent == null)
            {
                return base.CreateReader(settings);
            }
            IMFSourceReader ppSourceReader;
            IMFByteStream ppStream;
            var memoryStream = new MemoryStream();
            memoryStream.Write(fileContent, 0, fileContent.Length);
            var stream = new ManagedIStream(memoryStream);
            ExtendedMediaFoundationInterop.MFCreateMFByteStreamOnStream(stream, out ppStream);
            MediaFoundationInterop.MFCreateSourceReaderFromByteStream(ppStream, null, out ppSourceReader);
            ppSourceReader.SetStreamSelection(-2, false);
            ppSourceReader.SetStreamSelection(-3, true);
            ppSourceReader.SetCurrentMediaType(-3, IntPtr.Zero, new MediaType
                {
                    MajorType = MediaTypes.MFMediaType_Audio,
                    SubType = (settings.RequestFloatOutput ? AudioSubtypes.MFAudioFormat_Float : AudioSubtypes.MFAudioFormat_PCM)
                }.MediaFoundationObject);
            return ppSourceReader;
        }
    }
As you can see this CreateReader file uses MFCreateMFByteStreamOnStream for creating of byte stream (this method wasn't wrapper in MediaFoundationInterop, instead of it there is MFCreateMFByteStreamOnStreamEx which exist only in Windows 8). To wrap this method I added another class:
    internal static class ExtendedMediaFoundationInterop
    {
        [DllImport("mfplat.dll", PreserveSig = false)]
        public static extern void MFCreateMFByteStreamOnStream(IStream punkStream, out IMFByteStream ppByteStream);
    }
with this function. And also I've implemented simple COM IStream interface:
    internal sealed class ManagedIStream : IStream
    {
        public ManagedIStream(Stream stream)
        {
            if (stream == null)
                throw new ArgumentNullException("stream");

            _stream = stream;
        }

        void IStream.Read(byte[] buffer, int cb, IntPtr pcbRead)
        {
            int val = _stream.Read(buffer, 0, cb);
            if (pcbRead != IntPtr.Zero)
                Marshal.WriteInt32(pcbRead, val);
        }

        void IStream.Write(byte[] buffer, int cb, IntPtr pcbWritten)
        {
            _stream.Write(buffer, 0, cb);
            if (pcbWritten != IntPtr.Zero)
                Marshal.WriteInt32(pcbWritten, cb);
        }

        void IStream.Seek(long offset, int dwOrigin, IntPtr plibNewPosition)
        {
            SeekOrigin origin;
            switch (dwOrigin)
            {
                case 0: origin = SeekOrigin.Begin; break;
                case 1: origin = SeekOrigin.Current; break;
                case 2: origin = SeekOrigin.End; break;
                default: throw new ArgumentOutOfRangeException("dwOrigin");
            }

            long val = _stream.Seek(offset, origin);
            if (plibNewPosition != IntPtr.Zero)
                Marshal.WriteInt64(plibNewPosition, val);
        }

        void IStream.SetSize(long libNewSize)
        {
            _stream.SetLength(libNewSize);
        }

        void IStream.Stat(out STATSTG pstatstg, int grfStatFlag)
        {
            pstatstg = new STATSTG
            {
                type = 2,
                cbSize = _stream.Length,
            };

            if (_stream.CanRead && _stream.CanWrite)
                pstatstg.grfMode = 0x00000002;
            else if (_stream.CanWrite)
                pstatstg.grfMode = 0x00000001;
            else if (_stream.CanRead)
                pstatstg.grfMode = 0x00000000;
            else
                throw new IOException();
        }

        void IStream.Clone(out IStream ppstm)
        {
            ppstm = null;
            throw new NotSupportedException();
        }

        readonly Stream _stream;
    }
This works perfectly in Windows 7,
But I have an issue with extending of MediaFoundationEncoder:

Actually Windows Media Foundation supports in MFCreateSinkWriterFromURL creation of writer to the bytes stream, but all methods involved to encoding process are private and I'm not able to extend it properly, so I wanted to ask if such future will be planned in future releases? I really need it :)

Thanks, Roman Badiornyi.
Aug 21, 2013 at 3:44 PM
hi Roman, MediaFoundationEncoder is still a fairly new part of NAudio, and it would be nice to support encoding in memory. If you want to create your own patch to NAudio to support this I would definitely consider including it. My time to work on this has been rather limited recently. I may end up releasing NAudio 1.7 with it in its current state as a "preview" feature, and work a bit more in NAudio 1.8 on improving the API for Windows Store app situations.
Feb 1, 2014 at 10:30 AM
Edited Feb 1, 2014 at 2:30 PM
I have added this functionality to a fork in NAudio

Rather than use ExtendedMediaFounadtionReader I added the code directly into MediaFoundationReader.cs
MediaFoundationReader can now open streams.

https://naudio.codeplex.com/SourceControl/network/forks/K24A3/MediaFoundationStreamSupport/contribution/6137