using System;
using System.Runtime.InteropServices;
using NAudio.Dmo;
using NAudio.MediaFoundation;
namespace NAudio.Wave
{
///
/// The Media Foundation Resampler Transform
///
public class MediaFoundationResampler : MediaFoundationTransform
{
private int resamplerQuality;
private static bool IsPcmOrIeeeFloat(WaveFormat waveFormat)
{
var wfe = waveFormat as WaveFormatExtensible;
return waveFormat.Encoding == WaveFormatEncoding.Pcm ||
waveFormat.Encoding == WaveFormatEncoding.IeeeFloat ||
(wfe != null && (wfe.SubFormat == AudioSubtypes.MFAudioFormat_PCM
|| wfe.SubFormat == AudioSubtypes.MFAudioFormat_Float));
}
///
/// Creates the Media Foundation Resampler, allowing modifying of sample rate, bit depth and channel count
///
/// Source provider, must be PCM
/// Output format, must also be PCM
public MediaFoundationResampler(IWaveProvider sourceProvider, WaveFormat outputFormat)
: base(sourceProvider, outputFormat)
{
if (!IsPcmOrIeeeFloat(sourceProvider.WaveFormat))
throw new ArgumentException("Input must be PCM or IEEE float", "sourceProvider");
if (!IsPcmOrIeeeFloat(outputFormat))
throw new ArgumentException("Output must be PCM or IEEE float", "outputFormat");
MediaFoundationApi.Startup();
ResamplerQuality = 60; // maximum quality
// n.b. we will create the resampler COM object on demand in the Read method,
// to avoid threading issues but just
// so we can check it exists on the system we'll make one so it will throw an
// exception if not exists
var comObject = CreateResamplerComObject();
FreeComObject(comObject);
}
private static readonly Guid ResamplerClsid = new Guid("f447b69e-1884-4a7e-8055-346f74d6edb3");
private static readonly Guid IMFTransformIid = new Guid("bf94c121-5b05-4e6f-8000-ba598961414d");
private IMFActivate activate;
private void FreeComObject(object comObject)
{
if (activate != null) activate.ShutdownObject();
Marshal.ReleaseComObject(comObject);
}
private object CreateResamplerComObject()
{
#if NETFX_CORE
return CreateResamplerComObjectUsingActivator();
#else
return new ResamplerMediaComObject();
#endif
}
private object CreateResamplerComObjectUsingActivator()
{
var transformActivators = MediaFoundationApi.EnumerateTransforms(MediaFoundationTransformCategories.AudioEffect);
foreach (var activator in transformActivators)
{
Guid clsid;
activator.GetGUID(MediaFoundationAttributes.MFT_TRANSFORM_CLSID_Attribute, out clsid);
if (clsid.Equals(ResamplerClsid))
{
object comObject;
activator.ActivateObject(IMFTransformIid, out comObject);
activate = activator;
return comObject;
}
}
return null;
}
///
/// Creates a resampler with a specified target output sample rate
///
/// Source provider
/// Output sample rate
public MediaFoundationResampler(IWaveProvider sourceProvider, int outputSampleRate)
: this(sourceProvider, CreateOutputFormat(sourceProvider.WaveFormat, outputSampleRate))
{
}
///
/// Creates and configures the actual Resampler transform
///
/// A newly created and configured resampler MFT
protected override IMFTransform CreateTransform()
{
var comObject = CreateResamplerComObject();// new ResamplerMediaComObject();
var resamplerTransform = (IMFTransform)comObject;
var inputMediaFormat = MediaFoundationApi.CreateMediaTypeFromWaveFormat(sourceProvider.WaveFormat);
resamplerTransform.SetInputType(0, inputMediaFormat, 0);
Marshal.ReleaseComObject(inputMediaFormat);
var outputMediaFormat = MediaFoundationApi.CreateMediaTypeFromWaveFormat(outputWaveFormat);
resamplerTransform.SetOutputType(0, outputMediaFormat, 0);
Marshal.ReleaseComObject(outputMediaFormat);
//MFT_OUTPUT_STREAM_INFO pStreamInfo;
//resamplerTransform.GetOutputStreamInfo(0, out pStreamInfo);
// if pStreamInfo.dwFlags is 0, then it means we have to provide samples
// setup quality
var resamplerProps = (IWMResamplerProps)comObject;
// 60 is the best quality, 1 is linear interpolation
resamplerProps.SetHalfFilterLength(ResamplerQuality);
// may also be able to set this using MFPKEY_WMRESAMP_CHANNELMTX on the
// IPropertyStore interface.
// looks like we can also adjust the LPF with MFPKEY_WMRESAMP_LOWPASS_BANDWIDTH
return resamplerTransform;
}
///
/// Gets or sets the Resampler quality. n.b. set the quality before starting to resample.
/// 1 is lowest quality (linear interpolation) and 60 is best quality
///
public int ResamplerQuality
{
get { return resamplerQuality; }
set
{
if (value < 1 || value > 60)
throw new ArgumentOutOfRangeException("Resampler Quality must be between 1 and 60");
resamplerQuality = value;
}
}
private static WaveFormat CreateOutputFormat(WaveFormat inputFormat, int outputSampleRate)
{
WaveFormat outputFormat;
if (inputFormat.Encoding == WaveFormatEncoding.Pcm)
{
outputFormat = new WaveFormat(outputSampleRate,
inputFormat.BitsPerSample,
inputFormat.Channels);
}
else if (inputFormat.Encoding == WaveFormatEncoding.IeeeFloat)
{
outputFormat = WaveFormat.CreateIeeeFloatWaveFormat(outputSampleRate,
inputFormat.Channels);
}
else
{
throw new ArgumentException("Can only resample PCM or IEEE float");
}
return outputFormat;
}
///
/// Disposes this resampler
///
protected override void Dispose(bool disposing)
{
if (activate != null)
{
activate.ShutdownObject();
activate = null;
}
base.Dispose(disposing);
}
}
}