using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; using NAudio.MediaFoundation; using NAudio.Utils; namespace NAudio.Wave { /// /// Media Foundation Encoder class allows you to use Media Foundation to encode an IWaveProvider /// to any supported encoding format /// public class MediaFoundationEncoder : IDisposable { /// /// Queries the available bitrates for a given encoding output type, sample rate and number of channels /// /// Audio subtype - a value from the AudioSubtypes class /// The sample rate of the PCM to encode /// The number of channels of the PCM to encode /// An array of available bitrates in average bits per second public static int[] GetEncodeBitrates(Guid audioSubtype, int sampleRate, int channels) { return GetOutputMediaTypes(audioSubtype) .Where(mt => mt.SampleRate == sampleRate && mt.ChannelCount == channels) .Select(mt => mt.AverageBytesPerSecond*8) .Distinct() .OrderBy(br => br) .ToArray(); } /// /// Gets all the available media types for a particular /// /// Audio subtype - a value from the AudioSubtypes class /// An array of available media types that can be encoded with this subtype public static MediaType[] GetOutputMediaTypes(Guid audioSubtype) { MediaFoundationApi.Startup(); IMFCollection availableTypes; try { MediaFoundationInterop.MFTranscodeGetAudioOutputAvailableTypes( audioSubtype, _MFT_ENUM_FLAG.MFT_ENUM_FLAG_ALL, null, out availableTypes); } catch (COMException c) { if (c.GetHResult() == MediaFoundationErrors.MF_E_NOT_FOUND) { // Don't worry if we didn't find any - just means no encoder available for this type return new MediaType[0]; } else { throw; } } availableTypes.GetElementCount(out int count); var mediaTypes = new List(count); for (int n = 0; n < count; n++) { availableTypes.GetElement(n, out object mediaTypeObject); var mediaType = (IMFMediaType)mediaTypeObject; mediaTypes.Add(new MediaType(mediaType)); } Marshal.ReleaseComObject(availableTypes); return mediaTypes.ToArray(); } /// /// Helper function to simplify encoding Window Media Audio /// Should be supported on Vista and above (not tested) /// /// Input provider, must be PCM /// Output file path, should end with .wma /// Desired bitrate. Use GetEncodeBitrates to find the possibilities for your input type public static void EncodeToWma(IWaveProvider inputProvider, string outputFile, int desiredBitRate = 192000) { var mediaType = SelectMediaType(AudioSubtypes.MFAudioFormat_WMAudioV8, inputProvider.WaveFormat, desiredBitRate); if (mediaType == null) throw new InvalidOperationException("No suitable WMA encoders available"); using (var encoder = new MediaFoundationEncoder(mediaType)) { encoder.Encode(outputFile, inputProvider); } } /// /// Helper function to simplify encoding Window Media Audio /// Should be supported on Vista and above (not tested) /// /// Input provider, must be PCM /// Output stream /// Desired bitrate. Use GetEncodeBitrates to find the possibilities for your input type public static void EncodeToWma(IWaveProvider inputProvider, Stream outputStream, int desiredBitRate = 192000) { var mediaType = SelectMediaType(AudioSubtypes.MFAudioFormat_WMAudioV8, inputProvider.WaveFormat, desiredBitRate); if (mediaType == null) throw new InvalidOperationException("No suitable WMA encoders available"); using (var encoder = new MediaFoundationEncoder(mediaType)) { encoder.Encode(outputStream, inputProvider, TranscodeContainerTypes.MFTranscodeContainerType_ASF); } } /// /// Helper function to simplify encoding to MP3 /// By default, will only be available on Windows 8 and above /// /// Input provider, must be PCM /// Output file path, should end with .mp3 /// Desired bitrate. Use GetEncodeBitrates to find the possibilities for your input type public static void EncodeToMp3(IWaveProvider inputProvider, string outputFile, int desiredBitRate = 192000) { var mediaType = SelectMediaType(AudioSubtypes.MFAudioFormat_MP3, inputProvider.WaveFormat, desiredBitRate); if (mediaType == null) throw new InvalidOperationException("No suitable MP3 encoders available"); using (var encoder = new MediaFoundationEncoder(mediaType)) { encoder.Encode(outputFile, inputProvider); } } /// /// Helper function to simplify encoding to MP3 /// By default, will only be available on Windows 8 and above /// /// Input provider, must be PCM /// Output stream /// Desired bitrate. Use GetEncodeBitrates to find the possibilities for your input type public static void EncodeToMp3(IWaveProvider inputProvider, Stream outputStream, int desiredBitRate = 192000) { var mediaType = SelectMediaType(AudioSubtypes.MFAudioFormat_MP3, inputProvider.WaveFormat, desiredBitRate); if (mediaType == null) throw new InvalidOperationException("No suitable MP3 encoders available"); using (var encoder = new MediaFoundationEncoder(mediaType)) { encoder.Encode(outputStream, inputProvider, TranscodeContainerTypes.MFTranscodeContainerType_MP3); } } /// /// Helper function to simplify encoding to AAC /// By default, will only be available on Windows 7 and above /// /// Input provider, must be PCM /// Output file path, should end with .mp4 (or .aac on Windows 8) /// Desired bitrate. Use GetEncodeBitrates to find the possibilities for your input type public static void EncodeToAac(IWaveProvider inputProvider, string outputFile, int desiredBitRate = 192000) { // Information on configuring an AAC media type can be found here: // http://msdn.microsoft.com/en-gb/library/windows/desktop/dd742785%28v=vs.85%29.aspx var mediaType = SelectMediaType(AudioSubtypes.MFAudioFormat_AAC, inputProvider.WaveFormat, desiredBitRate); if (mediaType == null) throw new InvalidOperationException("No suitable AAC encoders available"); using (var encoder = new MediaFoundationEncoder(mediaType)) { // should AAC container have ADTS, or is that just for ADTS? // http://www.hydrogenaudio.org/forums/index.php?showtopic=97442 encoder.Encode(outputFile, inputProvider); } } /// /// Helper function to simplify encoding to AAC /// By default, will only be available on Windows 7 and above /// /// Input provider, must be PCM /// Output stream /// Desired bitrate. Use GetEncodeBitrates to find the possibilities for your input type public static void EncodeToAac(IWaveProvider inputProvider, Stream outputStream, int desiredBitRate = 192000) { // Information on configuring an AAC media type can be found here: // http://msdn.microsoft.com/en-gb/library/windows/desktop/dd742785%28v=vs.85%29.aspx var mediaType = SelectMediaType(AudioSubtypes.MFAudioFormat_AAC, inputProvider.WaveFormat, desiredBitRate); if (mediaType == null) throw new InvalidOperationException("No suitable AAC encoders available"); using (var encoder = new MediaFoundationEncoder(mediaType)) { // should AAC container have ADTS, or is that just for ADTS? // http://www.hydrogenaudio.org/forums/index.php?showtopic=97442 encoder.Encode(outputStream, inputProvider, TranscodeContainerTypes.MFTranscodeContainerType_MPEG4); } } /// /// Tries to find the encoding media type with the closest bitrate to that specified /// /// Audio subtype, a value from AudioSubtypes /// Your encoder input format (used to check sample rate and channel count) /// Your desired bitrate /// The closest media type, or null if none available public static MediaType SelectMediaType(Guid audioSubtype, WaveFormat inputFormat, int desiredBitRate) { MediaFoundationApi.Startup(); return GetOutputMediaTypes(audioSubtype) .Where(mt => mt.SampleRate == inputFormat.SampleRate && mt.ChannelCount == inputFormat.Channels) .Select(mt => new { MediaType = mt, Delta = Math.Abs(desiredBitRate - mt.AverageBytesPerSecond * 8) } ) .OrderBy(mt => mt.Delta) .Select(mt => mt.MediaType) .FirstOrDefault(); } public int DefaultReadBufferSize { get; set; } private readonly MediaType outputMediaType; private bool disposed; /// /// Creates a new encoder that encodes to the specified output media type /// /// Desired output media type public MediaFoundationEncoder(MediaType outputMediaType) { if (outputMediaType == null) throw new ArgumentNullException("outputMediaType"); this.outputMediaType = outputMediaType; } /// /// Encodes a file /// /// Output filename (container type is deduced from the filename) /// Input provider (should be PCM, some encoders will also allow IEEE float) public void Encode(string outputFile, IWaveProvider inputProvider) { if (inputProvider.WaveFormat.Encoding != WaveFormatEncoding.Pcm && inputProvider.WaveFormat.Encoding != WaveFormatEncoding.IeeeFloat) { throw new ArgumentException("Encode input format must be PCM or IEEE float"); } var inputMediaType = new MediaType(inputProvider.WaveFormat); var writer = CreateSinkWriter(outputFile); try { writer.AddStream(outputMediaType.MediaFoundationObject, out int streamIndex); // n.b. can get 0xC00D36B4 - MF_E_INVALIDMEDIATYPE here writer.SetInputMediaType(streamIndex, inputMediaType.MediaFoundationObject, null); PerformEncode(writer, streamIndex, inputProvider); } finally { if (writer != null) { Marshal.ReleaseComObject(writer); } if (inputMediaType.MediaFoundationObject != null) { Marshal.ReleaseComObject(inputMediaType.MediaFoundationObject); } } } /// /// Encodes a file /// /// Output stream /// Input provider (should be PCM, some encoders will also allow IEEE float) /// One of public void Encode(Stream outputStream, IWaveProvider inputProvider, Guid transcodeContainerType) { if (inputProvider.WaveFormat.Encoding != WaveFormatEncoding.Pcm && inputProvider.WaveFormat.Encoding != WaveFormatEncoding.IeeeFloat) { throw new ArgumentException("Encode input format must be PCM or IEEE float"); } var inputMediaType = new MediaType(inputProvider.WaveFormat); var writer = CreateSinkWriter(new ComStream(outputStream), transcodeContainerType); try { writer.AddStream(outputMediaType.MediaFoundationObject, out int streamIndex); // n.b. can get 0xC00D36B4 - MF_E_INVALIDMEDIATYPE here writer.SetInputMediaType(streamIndex, inputMediaType.MediaFoundationObject, null); PerformEncode(writer, streamIndex, inputProvider); } finally { if (writer != null) { Marshal.ReleaseComObject(writer); } if (inputMediaType.MediaFoundationObject != null) { Marshal.ReleaseComObject(inputMediaType.MediaFoundationObject); } } } private static IMFSinkWriter CreateSinkWriter(string outputFile) { // n.b. could try specifying the container type using attributes, but I think // it does a decent job of working it out from the file extension // n.b. AAC encode on Win 8 can have AAC extension, but use MP4 in win 7 // http://msdn.microsoft.com/en-gb/library/windows/desktop/dd389284%28v=vs.85%29.aspx IMFSinkWriter writer; var attributes = MediaFoundationApi.CreateAttributes(1); attributes.SetUINT32(MediaFoundationAttributes.MF_READWRITE_ENABLE_HARDWARE_TRANSFORMS, 1); try { MediaFoundationInterop.MFCreateSinkWriterFromURL(outputFile, null, attributes, out writer); } catch (COMException e) { if (e.GetHResult() == MediaFoundationErrors.MF_E_NOT_FOUND) { throw new ArgumentException("Was not able to create a sink writer for this file extension"); } throw; } finally { Marshal.ReleaseComObject(attributes); } return writer; } private static IMFSinkWriter CreateSinkWriter(IStream outputStream, Guid TranscodeContainerType) { // n.b. could try specifying the container type using attributes, but I think // it does a decent job of working it out from the file extension // n.b. AAC encode on Win 8 can have AAC extension, but use MP4 in win 7 // http://msdn.microsoft.com/en-gb/library/windows/desktop/dd389284%28v=vs.85%29.aspx IMFSinkWriter writer; var attributes = MediaFoundationApi.CreateAttributes(1); attributes.SetGUID(MediaFoundationAttributes.MF_TRANSCODE_CONTAINERTYPE, TranscodeContainerType); try { MediaFoundationInterop.MFCreateMFByteStreamOnStream(outputStream, out var ppByteStream); MediaFoundationInterop.MFCreateSinkWriterFromURL(null, ppByteStream, attributes, out writer); } finally { Marshal.ReleaseComObject(attributes); } return writer; } private void PerformEncode(IMFSinkWriter writer, int streamIndex, IWaveProvider inputProvider) { if (DefaultReadBufferSize== 0) DefaultReadBufferSize = inputProvider.WaveFormat.AverageBytesPerSecond * 4; var managedBuffer = new byte[DefaultReadBufferSize]; writer.BeginWriting(); long position = 0; long duration; do { duration = ConvertOneBuffer(writer, streamIndex, inputProvider, position, managedBuffer); position += duration; } while (duration > 0); writer.DoFinalize(); } private static long BytesToNsPosition(int bytes, WaveFormat waveFormat) { long nsPosition = (10000000L * bytes) / waveFormat.AverageBytesPerSecond; return nsPosition; } private long ConvertOneBuffer(IMFSinkWriter writer, int streamIndex, IWaveProvider inputProvider, long position, byte[] managedBuffer) { long durationConverted = 0; IMFMediaBuffer buffer = MediaFoundationApi.CreateMemoryBuffer(managedBuffer.Length); buffer.GetMaxLength(out var maxLength); IMFSample sample = MediaFoundationApi.CreateSample(); sample.AddBuffer(buffer); int read = inputProvider.Read(managedBuffer, 0, maxLength); if (read > 0) { buffer.Lock(out var ptr, out maxLength, out int currentLength); durationConverted = BytesToNsPosition(read, inputProvider.WaveFormat); Marshal.Copy(managedBuffer, 0, ptr, read); buffer.SetCurrentLength(read); buffer.Unlock(); sample.SetSampleTime(position); sample.SetSampleDuration(durationConverted); writer.WriteSample(streamIndex, sample); //writer.Flush(streamIndex); } Marshal.ReleaseComObject(sample); Marshal.ReleaseComObject(buffer); return durationConverted; } /// /// Disposes this instance /// /// protected void Dispose(bool disposing) { Marshal.ReleaseComObject(outputMediaType.MediaFoundationObject); } /// /// Disposes this instance /// public void Dispose() { if (!disposed) { disposed = true; Dispose(true); } GC.SuppressFinalize(this); } /// /// Finalizer /// ~MediaFoundationEncoder() { Dispose(false); } } }