diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..e69de29b diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 00000000..b9b7c8ad --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,3 @@ + + + diff --git a/NAudio.Alsa/AlsaCard.cs b/NAudio.Alsa/AlsaCard.cs new file mode 100644 index 00000000..6d6fa2dd --- /dev/null +++ b/NAudio.Alsa/AlsaCard.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using NAudio.Wave; +namespace NAudio.Wave.Alsa +{ + public class AlsaCard : IDisposable + { + public string IdString { get; private set; } + public int Index { get; private set; } + public string Id { get; private set; } + public string Driver { get; private set; } + public string Name { get; private set; } + public string LongName { get; private set; } + public string MixerName { get; private set; } + public string Components { get; private set; } + private IntPtr handle; + public IntPtr Handle + { + get => handle; + } + public List OutputDevices { get; private set; } = new List(); + public string[] GetDeviceNames() + { + return new string[] { "" }; + } + private AlsaCard(IntPtr handle) + { + this.handle = handle; + } + public static unsafe bool Create(string name, out AlsaCard card) + { + int error; + card = null; + IntPtr info = default; + AlsaInterop.CtlCardInfoMalloc(ref info); + if ((error = AlsaInterop.CtlOpen(out IntPtr handle, name, 0)) < 0) + { + AlsaInterop.CtlCardInfoFree(info); + Console.WriteLine(AlsaInterop.ErrorString(error)); + return false; + } + if ((error = AlsaInterop.CtlCardInfo(handle, info)) < 0) + { + AlsaInterop.CtlCardInfoFree(info); + Console.WriteLine(AlsaInterop.ErrorString(error)); + AlsaInterop.CtlClose(handle); + return false; + } + card = new AlsaCard(handle); + card.IdString = name; + int dev = -1; + do + { + if ((error = AlsaInterop.CtlPcmNextDevice(handle, ref dev)) < 0) + { + AlsaInterop.CtlCardInfoFree(info); + Console.WriteLine(AlsaInterop.ErrorString(error)); + return false; + } + if (dev < 0) + { + break; + } + if (AlsaOut.Create(card, dev, out AlsaOut device)) + { + card.OutputDevices.Add(device); + } + } while (true); + card.Name = AlsaInterop.CtlCardInfoGetName(info); + card.Index = AlsaInterop.CtlCardInfoGetCard(info); + card.Id = AlsaInterop.CtlCardInfoGetID(info); + card.Driver = AlsaInterop.CtlCardInfoGetDriver(info); + card.LongName = AlsaInterop.CtlCardInfoGetLongName(info); + card.MixerName = AlsaInterop.CtlCardInfoGetMixerName(info); + card.Components = AlsaInterop.CtlCardInfoGetComponents(info); + AlsaInterop.CtlCardInfoFree(info); + return true; + } + public void Dispose() + { + AlsaInterop.CtlClose(handle); + } + public override string ToString() + { + return $"card {Index}: {Id} [{Name}]"; + } + ~AlsaCard() + { + Console.WriteLine("Disposing ALSA Card"); + Dispose(); + } + } +} \ No newline at end of file diff --git a/NAudio.Alsa/AlsaDriver.cs b/NAudio.Alsa/AlsaDriver.cs new file mode 100644 index 00000000..e76c66cd --- /dev/null +++ b/NAudio.Alsa/AlsaDriver.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +namespace NAudio.Wave.Alsa +{ + public class AlsaDriver + { + private IntPtr _playbackPcm; + private List _cards = new List(); + public AlsaDriver() + { + EnumerateCards(); + } + public void EnumerateCards() + { + int card = -1; + if (AlsaInterop.NextCard(ref card) < 0 || card < 0) + { + throw new Exception("no soundcards found"); + } + while (card >= 0){ + string name = $"hw:{card.ToString()}"; + if (AlsaCard.Create(name, out AlsaCard card_obj)) + { + _cards.Add(card_obj); + Console.WriteLine(name); + } + AlsaInterop.NextCard(ref card); + } + } + internal static PCMFormat GetFormat(WaveFormat format) + { + switch (format.Encoding) + { + case WaveFormatEncoding.IeeeFloat: + switch (format.BitsPerSample) + { + case 32: + return PCMFormat.SND_PCM_FORMAT_FLOAT_LE; + } + break; + case WaveFormatEncoding.Pcm: + switch (format.BitsPerSample) + { + case 32: + return PCMFormat.SND_PCM_FORMAT_S32_LE; + case 24: + return PCMFormat.SND_PCM_FORMAT_S24_3LE; + case 16: + return PCMFormat.SND_PCM_FORMAT_S16_LE; + case 8: + return PCMFormat.SND_PCM_FORMAT_S8; + } + break; + case WaveFormatEncoding.MuLaw: + return PCMFormat.SND_PCM_FORMAT_MU_LAW; + case WaveFormatEncoding.ALaw: + return PCMFormat.SND_PCM_FORMAT_A_LAW; + } + return PCMFormat.SND_PCM_FORMAT_UNKNOWN; + } + } +} \ No newline at end of file diff --git a/NAudio.Alsa/AlsaException.cs b/NAudio.Alsa/AlsaException.cs new file mode 100644 index 00000000..6043e5f3 --- /dev/null +++ b/NAudio.Alsa/AlsaException.cs @@ -0,0 +1,13 @@ +using System; +using NAudio.Wave.Alsa; + +[Serializable] +public class AlsaException : Exception +{ + public AlsaException(int error) : base(AlsaInterop.ErrorString(error)) + { + } + public AlsaException(string message, int error) : base(string.Format("{0}: {1}", message, AlsaInterop.ErrorString(error))) + { + } +} \ No newline at end of file diff --git a/NAudio.Alsa/AlsaIn.cs b/NAudio.Alsa/AlsaIn.cs new file mode 100644 index 00000000..9b9820b4 --- /dev/null +++ b/NAudio.Alsa/AlsaIn.cs @@ -0,0 +1,176 @@ +using System; +using NAudio.Wave.Alsa; +using System.Threading.Tasks; +using System.Linq; + +namespace NAudio.Wave +{ + public class AlsaIn : AlsaPcm, IWaveIn + { + private bool recording; + private AlsaInterop.PcmCallback callback; + public void StartRecording() + { + if (recording) + { + throw new InvalidOperationException("Already recording"); + } + InitRecord(WaveFormat); + int error; + recording = true; + if ((error = AlsaInterop.PcmStart(Handle)) < 0) + { + throw new AlsaException("snd_pcm_start", error); + } + else if (!Async) + { + RecordPcmSync(); + } + } + public void StopRecording() + { + recording = false; + int error; + if ((error = AlsaInterop.PcmDrop(Handle)) < 0) + { + RaiseRecordingStopped(new AlsaException(error)); + } + else + { + RaiseRecordingStopped(null); + } + } + public WaveFormat WaveFormat { get; set;} + public event EventHandler DataAvailable; + public event EventHandler RecordingStopped; + public void Dispose() + { + if (isDisposed) + { + return; + } + isDisposed = true; + AlsaInterop.PcmHwParamsFree(HwParams); + AlsaInterop.PcmSwParamsFree(SwParams); + AlsaInterop.PcmClose(Handle); + } + public AlsaIn(string pcm_name) + { + int error; + if ((error = AlsaInterop.PcmOpen(out Handle, pcm_name, PCMStream.SND_PCM_STREAM_CAPTURE, 0)) < 0) + { + throw new AlsaException("snd_pcm_open", error); + } + callback = Callback; + ulong buffer_size = PERIOD_SIZE * PERIOD_QUANTITY; + if ((error = AlsaInterop.AsyncAddPcmHandler(out IntPtr handler, Handle, callback, default)) != 0) + { + Async = false; + } + else + { + Async = true; + } + } + private void InitRecord(WaveFormat waveFormat) + { + GetHardwareParams(); + int error; + int dir = 0; + uint periods = PERIOD_QUANTITY; + if (isInitialized) + { + throw new InvalidOperationException("Already initialized"); + } + isInitialized = true; + InitBuffers(); + ulong buffer_size = (ulong)WaveBuffer.Length; + SetInterleavedAccess(); + SetFormat(waveFormat); + SetPeriods(ref periods, ref dir); + SetBufferSize(ref buffer_size); + SetHardwareParams(); + GetSoftwareParams(); + if ((error = AlsaInterop.PcmSwParamsSetStartThreshold(Handle, SwParams, buffer_size - PERIOD_SIZE)) < 0) + { + throw new AlsaException(error); + } + if ((error = AlsaInterop.PcmSwParamsSetAvailMin(Handle, SwParams, PERIOD_SIZE)) < 0) + { + throw new AlsaException(error); + } + SetSoftwareParams(); + AlsaInterop.PcmPrepare(Handle); + } + public AlsaIn() : this("default") + { + } + ~AlsaIn() + { + Dispose(); + } + private void Callback(IntPtr cb_info) + { + ReadPcm(); + } + private void ReadPcm() + { + ulong avail = AlsaInterop.PcmAvailUpdate(Handle); + int bits_per_frame = WaveFormat.BitsPerSample * WaveFormat.Channels; + int frame_bytes = bits_per_frame / 8; + while (avail >= PERIOD_SIZE) + { + int frames = AlsaInterop.PcmReadI(Handle, WaveBuffer, PERIOD_SIZE); + if (frames < 0) + { + recording = false; + RaiseRecordingStopped(new AlsaException(frames)); + } + if (!Async) SwapBuffers(); + RaiseDataAvailable(WaveBuffer, frames * frame_bytes); + avail = AlsaInterop.PcmAvailUpdate(Handle); + } + } + private void RecordPcmSync() + { + Task.Run(() => { + while (recording) + { + ReadPcm(); + } + }); + } + private void SetFormat(WaveFormat format) + { + int error; + if (GetValidWaveFormats().Contains(format)) + { + WaveFormat = format; + } + else + { + throw new NotSupportedException($"{format} not supported"); + } + var sampleformat = AlsaDriver.GetFormat(WaveFormat); + if ((error = AlsaInterop.PcmHwParamsSetFormat(Handle, HwParams, sampleformat)) < 0) + { + throw new AlsaException(error); + } + int desiredSampleRate = WaveFormat.SampleRate; + SetSampleRate((uint)desiredSampleRate); + SetNumberOfChannels((uint)WaveFormat.Channels); + } + private void RaiseRecordingStopped(Exception e) + { + var handler = RecordingStopped; + if (handler != null) + { + handler(this, new StoppedEventArgs(e)); + } + } + private void RaiseDataAvailable(byte[] buffer, int bytes) + { + DataAvailable?.Invoke(this, new WaveInEventArgs(buffer, bytes)); + } + } +} \ No newline at end of file diff --git a/NAudio.Alsa/AlsaOut.cs b/NAudio.Alsa/AlsaOut.cs new file mode 100644 index 00000000..c8c75287 --- /dev/null +++ b/NAudio.Alsa/AlsaOut.cs @@ -0,0 +1,265 @@ +using System; +using NAudio.Wave.Alsa; +using NAudio.Wave.SampleProviders; +using System.Threading.Tasks; +namespace NAudio.Wave +{ + + public class AlsaOut : AlsaPcm, IWavePlayer + { + private IWaveProvider sourceStream; + private PlaybackState playbackState; + private readonly AlsaInterop.PcmCallback callback; + public WaveFormat OutputWaveFormat { get; private set; } + public event EventHandler PlaybackStopped; + public float Volume + { + get => 1.0f; + set => throw new NotImplementedException(""); + } + public PlaybackState PlaybackState + { + get => playbackState; + } + public bool HasReachedEnd { get; private set; } + public void Dispose() + { + if (isDisposed) + { + return; + } + isDisposed = true; + AlsaInterop.PcmClose(Handle); + if (HwParams != default) AlsaInterop.PcmHwParamsFree(HwParams); + if (SwParams != default) AlsaInterop.PcmSwParamsFree(SwParams); + } + + public void Init(IWaveProvider waveProvider) + { + InitPlayback(waveProvider); + } + public void Play() + { + if (playbackState == PlaybackState.Paused) + { + playbackState = PlaybackState.Playing; + int error; + if ((error = AlsaInterop.PcmPause(Handle, 0)) < 0) + { + throw new AlsaException(error); + } + if (!Async) + { + PlayPcmSync(); + } + } + else if (playbackState != PlaybackState.Playing) + { + if (Async) + { + int error; + if ((error = AlsaInterop.PcmStart(Handle)) < 0) + { + error = AlsaInterop.PcmRecover(Handle, error, 0); + } + if (error < 0) + { + throw new AlsaException("snd_pcm_start", error); + } + playbackState = PlaybackState.Playing; + HasReachedEnd = false; + return; + } + else + { + playbackState = PlaybackState.Playing; + HasReachedEnd = false; + BufferUpdate(); + PlayPcmSync(); + } + } + } + public void Stop() + { + playbackState = PlaybackState.Stopped; + AlsaInterop.PcmDrop(Handle); + HasReachedEnd = false; + RaisePlaybackStopped(null); + } + public void Pause() + { + if (playbackState == PlaybackState.Playing) + { + playbackState = PlaybackState.Paused; + int error; + if ((error = AlsaInterop.PcmPause(Handle, 1)) < 0) + { + throw new AlsaException(error); + } + } + } + public AlsaOut(string pcm_name) + { + int error; + if ((error = AlsaInterop.PcmOpen(out Handle, pcm_name, PCMStream.SND_PCM_STREAM_PLAYBACK, 0)) < 0) + { + throw new AlsaException("snd_pcm_open", error); + } + callback = Callback; + ulong buffer_size = PERIOD_SIZE * PERIOD_QUANTITY; + if ((error = AlsaInterop.AsyncAddPcmHandler(out IntPtr handler, Handle, callback, default)) != 0) + { + Async = false; + } + else + { + Async = true; + } + GetHardwareParams(); + } + public AlsaOut() : this("default") + { + } + ~AlsaOut() + { + Dispose(); + } + public static bool Create(AlsaCard card, int device_num, out AlsaOut device) + { + device = null; + return false; + } + public static bool Create(string pcm_name, out AlsaOut device) + { + device = null; + try + { + device = new AlsaOut(pcm_name); + return true; + } + catch + { + return false; + } + } + public void InitPlayback(IWaveProvider waveProvider) + { + int error; + int dir = 0; + uint periods = PERIOD_QUANTITY; + if (isInitialized) + { + throw new InvalidOperationException("Already initialized this PCM"); + } + isInitialized = true; + if (waveProvider != null) + { + sourceStream = waveProvider; + InitBuffers(); + ulong buffer_size = (ulong)WaveBuffer.Length; + SetInterleavedAccess(); + SetFormat(waveProvider); + SetPeriods(ref periods, ref dir); + SetBufferSize(ref buffer_size); + SetHardwareParams(); + GetSoftwareParams(); + if ((error = AlsaInterop.PcmSwParamsSetStartThreshold(Handle, SwParams, buffer_size - PERIOD_SIZE)) < 0) + { + throw new AlsaException(error); + } + if ((error = AlsaInterop.PcmSwParamsSetAvailMin(Handle, SwParams, PERIOD_SIZE)) < 0) + { + throw new AlsaException(error); + } + SetSoftwareParams(); + _ = State; // idk... it doesn't work without this + if (Async) + { + AlsaInterop.PcmWriteI(Handle, WaveBuffer, 2 * PERIOD_SIZE); + } + } + } + public void SetFormat(IWaveProvider waveProvider) + { + int error; + var format = AlsaDriver.GetFormat(waveProvider.WaveFormat); + if (AlsaInterop.PcmHwParamsTestFormat(Handle, HwParams, format) == 0) + { + OutputWaveFormat = waveProvider.WaveFormat; + AlsaInterop.PcmHwParamsSetFormat(Handle, HwParams, format); + } + else if ((error = AlsaInterop.PcmHwParamsTestFormat(Handle, HwParams, PCMFormat.SND_PCM_FORMAT_S32_LE)) == 0) + { + AlsaInterop.PcmHwParamsSetFormat(Handle, HwParams, PCMFormat.SND_PCM_FORMAT_S32_LE); + sourceStream = new SampleToWaveProvider32(new WaveToSampleProvider(waveProvider)); + OutputWaveFormat = sourceStream.WaveFormat; + } + else if ((error = AlsaInterop.PcmHwParamsTestFormat(Handle, HwParams, PCMFormat.SND_PCM_FORMAT_S16_LE)) == 0) + { + AlsaInterop.PcmHwParamsSetFormat(Handle, HwParams, PCMFormat.SND_PCM_FORMAT_S16_LE); + sourceStream = new SampleToWaveProvider16(new WaveToSampleProvider(waveProvider)); + OutputWaveFormat = sourceStream.WaveFormat; + } + else + { + AlsaInterop.PcmHwParamsFree(HwParams); + throw new AlsaException(error); + } + int desiredSampleRate = waveProvider.WaveFormat.SampleRate; + SetSampleRate((uint)desiredSampleRate); + SetNumberOfChannels((uint)waveProvider.WaveFormat.Channels); + } + private void PlayPcmSync() + { + Task.Run(() => { + while (playbackState == PlaybackState.Playing) + { + WritePcm(); + } + }); + } + private void WritePcm() + { + ulong avail = AlsaInterop.PcmAvailUpdate(Handle); + while (avail >= PERIOD_SIZE) + { + if (!Async) SwapBuffers(); + int error = AlsaInterop.PcmWriteI(Handle, WaveBuffer, PERIOD_SIZE); + BufferUpdate(); + if (error < 0) + { + if ((error = AlsaInterop.PcmRecover(Handle, error, 0)) < 0) + { + RaisePlaybackStopped(new AlsaException(error)); + } + } + avail = AlsaInterop.PcmAvailUpdate(Handle); + } + } + private void Callback(IntPtr callback) + { + WritePcm(); + } + private void BufferUpdate() + { + int read = sourceStream.Read(WaveBuffer, 0, WaveBuffer.Length); + if (read < WaveBuffer.Length) + { + Array.Clear(WaveBuffer, read, WaveBuffer.Length - read); + } + if (read == 0) + { + Stop(); + HasReachedEnd = true; + } + } + private void RaisePlaybackStopped(Exception e) + { + var handler = PlaybackStopped; + if (handler != null) + { + handler(this, new StoppedEventArgs(e)); + } + } + } +} diff --git a/NAudio.Alsa/AlsaPcm.cs b/NAudio.Alsa/AlsaPcm.cs new file mode 100644 index 00000000..73122840 --- /dev/null +++ b/NAudio.Alsa/AlsaPcm.cs @@ -0,0 +1,317 @@ +using NAudio.Wave.Alsa; +using NAudio.Wave; +using System; +using System.Collections.Generic; +using System.Linq; + +public abstract class AlsaPcm +{ + protected static readonly uint[] rates = + { + 8000, + 11025, + 16000, + 22050, + 32000, + 44100, + 48000, + 64000, + 64000, + 88200, + 96000, + 176400, + 192000 + }; + private bool isBufferInit = false; + private int numBuffers = 2; + protected const uint PERIOD_QUANTITY = 8; + protected const ulong PERIOD_SIZE = 1024; + protected IntPtr Handle = default; + protected IntPtr HwParams = default; + protected IntPtr SwParams = default; + protected int BufferNum; + protected byte[] WaveBuffer; + protected byte[][] Buffers; + protected bool isInitialized = false; + protected bool isDisposed = false; + internal PCMState State { get=> AlsaInterop.PcmState(Handle); } + public int Card { get; private set; } + public uint Device { get; private set; } + public string Id { get; private set; } + public string Name { get; private set; } + public int NumberOfBuffers + { + get =>numBuffers; + set + { + if (!isBufferInit) + { + numBuffers = value; + } + } + } + public bool Async { get; protected set; } + + protected void GetHardwareParams() + { + if (HwParams != default) + { + AlsaInterop.PcmHwParamsFree(HwParams); + } + AlsaInterop.PcmHwParamsMalloc(out HwParams); + AlsaInterop.PcmHwParamsAny(Handle, HwParams); + } + protected void SetHardwareParams() + { + int error; + if ((error = AlsaInterop.PcmHwParams(Handle, HwParams)) != 0) + { + AlsaInterop.PcmHwParamsFree(HwParams); + throw new AlsaException(error); + } + } + protected void GetSoftwareParams() + { + if (SwParams != default) + { + AlsaInterop.PcmSwParamsFree(SwParams); + } + AlsaInterop.PcmSwParamsMalloc(out SwParams); + AlsaInterop.PcmSwParamsCurrent(Handle, SwParams); + } + protected void SetSoftwareParams() + { + int error; + if ((error = AlsaInterop.PcmSwParams(Handle, SwParams)) < 0) + { + AlsaInterop.PcmSwParamsFree(SwParams); + throw new AlsaException(error); + } + } + protected void SetInterleavedAccess() + { + int error; + if ((error = AlsaInterop.PcmHwParamsTestAccess(Handle, HwParams, PCMAccess.SND_PCM_ACCESS_RW_INTERLEAVED)) != 0) + { + AlsaInterop.PcmHwParamsFree(HwParams); + throw new AlsaException(error); + } + AlsaInterop.PcmHwParamsSetAccess(Handle, HwParams, PCMAccess.SND_PCM_ACCESS_RW_INTERLEAVED); + } + protected void SetSampleRate(uint sampleRate) + { + int error; + if ((error = AlsaInterop.PcmHwParamsSetRate(Handle, HwParams, sampleRate, 0)) != 0) + { + AlsaInterop.PcmHwParamsFree(HwParams); + throw new AlsaException(error); + } + } + protected void SetNumberOfChannels(uint numChannels) + { + int error; + if ((error = AlsaInterop.PcmHwParamsSetChannels(Handle, HwParams, numChannels)) != 0) + { + AlsaInterop.PcmHwParamsFree(HwParams); + throw new AlsaException(error); + } + } + protected void SetPeriods(ref uint periods, ref int dir) + { + int error; + if ((error = AlsaInterop.PcmHwParamsSetPeriodsNear(Handle, HwParams, ref periods, ref dir)) != 0) + { + AlsaInterop.PcmHwParamsFree(HwParams); + throw new AlsaException(error); + } + } + protected void SetBufferSize(ref ulong buffer_size) + { + int error; + if ((error = AlsaInterop.PcmHwParamsSetBufferSizeNear(Handle, HwParams, ref buffer_size)) != 0) + { + AlsaInterop.PcmHwParamsFree(HwParams); + throw new AlsaException(error); + } + } + protected static ulong GetSampleSize(WaveFormat format) + { + return (ulong)(format.BitsPerSample / 8 * format.Channels); + } + private static int[] GetValidChannelValues(IntPtr Handle, IntPtr HwParams) + { + AlsaInterop.PcmHwParamsGetChannelsMin(HwParams, out uint min); + AlsaInterop.PcmHwParamsGetChannelsMax(HwParams, out uint max); + int[] result = new int[0]; + for (uint i = min; i <= max; i++) + { + if (AlsaInterop.PcmHwParamsTestChannels(Handle, HwParams, i) == 0) + { + int[] newresult = new int[result.Length + 1]; + Array.Copy(result, newresult, result.Length); + newresult[result.Length] = (int)i; + result = newresult; + } + } + return result; + } + private static int[] GetValidSampleRates(IntPtr Handle, IntPtr HwParams) + { + int[] result = new int[0]; + for (uint i = 0; i < rates.Length; i++) + { + var rate = rates[i]; + if (AlsaInterop.PcmHwParamsTestRate(Handle, HwParams, rate, 0) == 0) + { + int[] newresult = new int[result.Length + 1]; + Array.Copy(result, newresult, result.Length); + newresult[result.Length] = (int)rates[i]; + result = newresult; + } + } + return result; + } + private static PCMFormat[] GetValidFormats(IntPtr Handle, IntPtr HwParams) + { + PCMFormat[] result = new PCMFormat[0]; + for (PCMFormat i = 0; i < PCMFormat.SND_PCM_FORMAT_LAST; i++) + { + if (AlsaInterop.PcmHwParamsTestFormat(Handle, HwParams, i) == 0) + { + PCMFormat[] newresult = new PCMFormat[result.Length + 1]; + Array.Copy(result, newresult, result.Length); + newresult[result.Length] = i; + result = newresult; + } + } + return result; + } + private static WaveFormat[] GetValidWaveFormats(IntPtr Handle, IntPtr HwParams) + { + var valid_rates = GetValidSampleRates(Handle, HwParams); + var valid_channels = GetValidChannelValues(Handle, HwParams); + var valid_formats = GetValidFormats(Handle, HwParams); + List valid_waveformats = new List(); + for (int i = 0; i < valid_formats.Length; i++) + { + switch (valid_formats[i]) + { + case PCMFormat.SND_PCM_FORMAT_FLOAT_LE: + for (int j = 0; j < valid_rates.Length; j++) + { + for (int k = 0; k < valid_channels.Length; k++) + { + valid_waveformats.Add(WaveFormat.CreateIeeeFloatWaveFormat(valid_rates[j], valid_channels[k])); + } + } + break; + case PCMFormat.SND_PCM_FORMAT_U8: + for (int j = 0; j < valid_rates.Length; j++) + { + for (int k = 0; k < valid_channels.Length; k++) + { + valid_waveformats.Add(new WaveFormat(valid_rates[j], 8, valid_channels[k])); + } + } + break; + case PCMFormat.SND_PCM_FORMAT_S16_LE: + for (int j = 0; j < valid_rates.Length; j++) + { + for (int k = 0; k < valid_channels.Length; k++) + { + valid_waveformats.Add(new WaveFormat(valid_rates[j], 16, valid_channels[k])); + } + } + break; + case PCMFormat.SND_PCM_FORMAT_S24_3LE: + for (int j = 0; j < valid_rates.Length; j++) + { + for (int k = 0; k < valid_channels.Length; k++) + { + valid_waveformats.Add(new WaveFormat(valid_rates[j], 24, valid_channels[k])); + } + } + break; + case PCMFormat.SND_PCM_FORMAT_S32_LE: + for (int j = 0; j < valid_rates.Length; j++) + { + for (int k = 0; k < valid_channels.Length; k++) + { + valid_waveformats.Add(new WaveFormat(valid_rates[j], 32, valid_channels[k])); + } + } + break; + case PCMFormat.SND_PCM_FORMAT_A_LAW: + for (int j = 0; j < valid_rates.Length; j++) + { + for (int k = 0; k < valid_channels.Length; k++) + { + valid_waveformats.Add(WaveFormat.CreateALawFormat(valid_rates[j], valid_channels[k])); + } + } + break; + case PCMFormat.SND_PCM_FORMAT_MU_LAW: + for (int j = 0; j < valid_rates.Length; j++) + { + for (int k = 0; k < valid_channels.Length; k++) + { + valid_waveformats.Add(WaveFormat.CreateMuLawFormat(valid_rates[j], valid_channels[k])); + } + } + break; + + } + } + return valid_waveformats.ToArray(); + } + public WaveFormat[] GetValidWaveFormats() + { + return GetValidWaveFormats(Handle, HwParams); + } + public WaveFormat GetCurrentWaveFormat() + { + WaveFormat result = null; + var formats = GetValidWaveFormats(); + if (formats.Length == 1) + { + result = formats[0]; + } + return result; + } + public static bool TestWaveFormat(WaveFormat waveFormat, IntPtr Handle) + { + AlsaInterop.PcmHwParamsMalloc(out IntPtr hwparams); + AlsaInterop.PcmHwParamsAny(Handle, hwparams); + var result = GetValidWaveFormats(Handle, hwparams).Contains(waveFormat); + AlsaInterop.PcmHwParamsFree(hwparams); + return result; + } + public bool TestWaveFormat(WaveFormat waveFormat) + { + return TestWaveFormat(waveFormat, Handle); + } + protected void SwapBuffers() + { + BufferNum = ++BufferNum % NumberOfBuffers; + WaveBuffer = Buffers[BufferNum]; + } + protected void InitBuffers() + { + isBufferInit = true; + int error; + ulong buffer_size = PERIOD_SIZE * PERIOD_QUANTITY; + if (!Async) + { + Buffers = new byte[NumberOfBuffers][]; + for (int i = 0; i < NumberOfBuffers; i++) + { + Buffers[i] = new byte[buffer_size]; + } + WaveBuffer = Buffers[BufferNum]; + } + else + { + WaveBuffer = new byte[buffer_size]; + } + } +} diff --git a/NAudio.Alsa/Interop/Alsa.Enums.cs b/NAudio.Alsa/Interop/Alsa.Enums.cs new file mode 100644 index 00000000..ecb82f29 --- /dev/null +++ b/NAudio.Alsa/Interop/Alsa.Enums.cs @@ -0,0 +1,105 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +internal enum PCMStream +{ + SND_PCM_STREAM_PLAYBACK = 0, + SND_PCM_STREAM_CAPTURE = 1, + SND_PCM_STREAM_LAST = SND_PCM_STREAM_CAPTURE, +} + +internal enum PCMFormat +{ + SND_PCM_FORMAT_UNKNOWN = -1, + SND_PCM_FORMAT_S8 = 0, + SND_PCM_FORMAT_U8 = 1, + SND_PCM_FORMAT_S16_LE = 2, + SND_PCM_FORMAT_S16_BE = 3, + SND_PCM_FORMAT_U16_LE = 4, + SND_PCM_FORMAT_U16_BE = 5, + SND_PCM_FORMAT_S24_LE = 6, + SND_PCM_FORMAT_S24_BE = 7, + SND_PCM_FORMAT_U24_LE = 8, + SND_PCM_FORMAT_U24_BE = 9, + SND_PCM_FORMAT_S32_LE = 10, + SND_PCM_FORMAT_S32_BE = 11, + SND_PCM_FORMAT_U32_LE = 12, + SND_PCM_FORMAT_U32_BE = 13, + SND_PCM_FORMAT_FLOAT_LE = 14, + SND_PCM_FORMAT_FLOAT_BE = 15, + SND_PCM_FORMAT_FLOAT64_LE = 16, + SND_PCM_FORMAT_FLOAT64_BE = 17, + SND_PCM_FORMAT_IEC958_SUBFRAME_LE = 18, + SND_PCM_FORMAT_IEC958_SUBFRAME_BE = 19, + SND_PCM_FORMAT_MU_LAW = 20, + SND_PCM_FORMAT_A_LAW = 21, + SND_PCM_FORMAT_IMA_ADPCM = 22, + SND_PCM_FORMAT_MPEG = 23, + SND_PCM_FORMAT_GSM = 24, + SND_PCM_FORMAT_SPECIAL = 31, + SND_PCM_FORMAT_S24_3LE = 32, + SND_PCM_FORMAT_S24_3BE = 33, + SND_PCM_FORMAT_U24_3LE = 34, + SND_PCM_FORMAT_U24_3BE = 35, + SND_PCM_FORMAT_S20_3LE = 36, + SND_PCM_FORMAT_S20_3BE = 37, + SND_PCM_FORMAT_U20_3LE = 38, + SND_PCM_FORMAT_U20_3BE = 39, + SND_PCM_FORMAT_S18_3LE = 40, + SND_PCM_FORMAT_S18_3BE = 41, + SND_PCM_FORMAT_U18_3LE = 42, + SND_PCM_FORMAT_U18_3BE = 43, + SND_PCM_FORMAT_G723_24 = 44, + SND_PCM_FORMAT_G723_24_1B = 45, + SND_PCM_FORMAT_G723_40 = 46, + SND_PCM_FORMAT_G723_40_1B = 47, + SND_PCM_FORMAT_DSD_U8 = 48, + SND_PCM_FORMAT_DSD_U16_LE = 49, + SND_PCM_FORMAT_DSD_U32_LE = 50, + SND_PCM_FORMAT_DSD_U16_BE = 51, + SND_PCM_FORMAT_DSD_U32_BE = 52, + SND_PCM_FORMAT_LAST = SND_PCM_FORMAT_DSD_U32_BE, +} + +internal enum PCMAccess +{ + SND_PCM_ACCESS_MMAP_INTERLEAVED = 0, + SND_PCM_ACCESS_MMAP_NONINTERLEAVED = 1, + SND_PCM_ACCESS_MMAP_COMPLEX = 2, + SND_PCM_ACCESS_RW_INTERLEAVED = 3, + SND_PCM_ACCESS_RW_NONINTERLEAVED = 4, + SND_PCM_ACCESS_LAST = SND_PCM_ACCESS_RW_NONINTERLEAVED, +} + +internal enum MixerSimpleElementChannelIdentifier +{ + SND_MIXER_SCHN_UNKNOWN = -1, + SND_MIXER_SCHN_FRONT_LEFT = 0, + SND_MIXER_SCHN_FRONT_RIGHT = 1, + SND_MIXER_SCHN_REAR_LEFT = 2, + SND_MIXER_SCHN_REAR_RIGHT = 3, + SND_MIXER_SCHN_FRONT_CENTER = 4, + SND_MIXER_SCHN_WOOFER = 5, + SND_MIXER_SCHN_SIDE_LEFT = 6, + SND_MIXER_SCHN_SIDE_RIGHT = 7, + SND_MIXER_SCHN_REAR_CENTER = 8, + SND_MIXER_SCHN_LAST = 31, + SND_MIXER_SCHN_MONO = SND_MIXER_SCHN_FRONT_LEFT +} + +internal enum PCMState +{ + SND_PCM_STATE_OPEN = 0, + SND_PCM_STATE_SETUP, + SND_PCM_STATE_PREPARED, + SND_PCM_STATE_RUNNING, + SND_PCM_STATE_XRUN, + SND_PCM_STATE_DRAINING, + SND_PCM_STATE_PAUSED, + SND_PCM_STATE_SUSPENDED, + SND_PCM_STATE_DISCONNECTED, + SND_PCM_STATE_LAST = SND_PCM_STATE_DISCONNECTED, + SND_PCM_STATE_PRIVATE1 = 1024 + +} \ No newline at end of file diff --git a/NAudio.Alsa/Interop/Alsa.cs b/NAudio.Alsa/Interop/Alsa.cs new file mode 100644 index 00000000..e6f6c44b --- /dev/null +++ b/NAudio.Alsa/Interop/Alsa.cs @@ -0,0 +1,191 @@ +using System; +using System.Runtime.InteropServices; +namespace NAudio.Wave.Alsa +{ + internal class AlsaInterop + { + public delegate void PcmCallback(IntPtr handler); + private const string AlsaLibrary = "libasound"; + [DllImport(AlsaLibrary, EntryPoint = "snd_card_next")] + internal static extern int NextCard(ref int rcard); + [DllImport(AlsaLibrary, EntryPoint = "snd_ctl_open")] + internal static extern int CtlOpen(out IntPtr ctlp, string name, int mode); + [DllImport(AlsaLibrary, EntryPoint = "snd_ctl_close")] + internal static extern int CtlClose(IntPtr ctl); + [DllImport(AlsaLibrary, EntryPoint = "snd_ctl_card_info")] + internal static unsafe extern int CtlCardInfo(IntPtr ctlp, IntPtr info); + [DllImport(AlsaLibrary, EntryPoint = "snd_ctl_card_info_free")] + internal static unsafe extern int CtlCardInfoFree(IntPtr info); + [DllImport(AlsaLibrary, EntryPoint = "snd_ctl_card_info_get_card")] + internal static extern int CtlCardInfoGetCard(IntPtr info); + [DllImport(AlsaLibrary, CharSet = CharSet.Ansi, EntryPoint = "snd_ctl_card_info_get_id")] + private static extern IntPtr ctlCardInfoGetID(IntPtr info); + [DllImport(AlsaLibrary, CharSet = CharSet.Ansi, EntryPoint = "snd_ctl_card_info_get_driver")] + private static extern IntPtr ctlCardInfoGetDriver(IntPtr info); + [DllImport(AlsaLibrary, CharSet = CharSet.Ansi, EntryPoint = "snd_ctl_card_info_get_name")] + private static extern IntPtr ctlCardInfoGetName(IntPtr info); + [DllImport(AlsaLibrary, CharSet = CharSet.Ansi, EntryPoint = "snd_ctl_card_info_get_longname")] + private static extern IntPtr ctlCardInfoGetLongName(IntPtr info); + [DllImport(AlsaLibrary, CharSet = CharSet.Ansi, EntryPoint = "snd_ctl_card_info_get_mixername")] + private static extern IntPtr ctlCardInfoGetMixerName(IntPtr info); + [DllImport(AlsaLibrary, CharSet = CharSet.Ansi, EntryPoint = "snd_ctl_card_info_get_components")] + private static extern IntPtr ctlCardInfoGetComponents(IntPtr info); + [DllImport(AlsaLibrary, EntryPoint = "snd_ctl_card_info_malloc")] + internal static unsafe extern void CtlCardInfoMalloc(ref IntPtr info); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_info_malloc")] + internal static extern void PcmInfoMalloc(out IntPtr pcmInfo); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_info_free")] + internal static extern void PcmInfoFree(IntPtr pcmInfo); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_stream_name")] + internal static extern string PcmStreamName(IntPtr stream); + [DllImport(AlsaLibrary, EntryPoint = "snd_ctl_pcm_next_device")] + internal static extern int CtlPcmNextDevice(IntPtr ctl, ref int device); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_info_set_device")] + internal static extern void PcmInfoSetDevice(IntPtr pcmInfo, uint val); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_info_set_subdevice")] + internal static extern void PcmInfoSetSubdevice(IntPtr pcmInfo, uint val); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_info_set_stream")] + internal static extern void PcmInfoSetStream(IntPtr pcmInfo, PCMStream stream); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_info_get_id")] + private static extern IntPtr pcmInfoGetID(IntPtr pcmInfo); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_info_get_name")] + private static extern IntPtr pcmInfoGetName(IntPtr pcmInfo); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_info_get_stream")] + internal static extern PCMStream PcmInfoGetStream(IntPtr pcmInfo); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_info_get_subdevice_name")] + private static extern IntPtr pcmInfoGetSubdeviceName(IntPtr pcmInfo); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_info_get_subdevices_avail")] + internal static extern uint PcmInfoGetSubdevicesAvailable(IntPtr pcmInfo); + [DllImport(AlsaLibrary, EntryPoint = "snd_ctl_pcm_info")] + internal static extern int CtlPcmInfo(IntPtr ctl, IntPtr info); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_info_get_subdevices_count")] + internal static extern uint PcmInfoGetSubdevicesCount(IntPtr pcmInfo); + [DllImport(AlsaLibrary, EntryPoint = "snd_strerror")] + private static extern IntPtr strError(int error); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_open")] + internal static extern int PcmOpen(out IntPtr pcm, string name, PCMStream stream, int mode); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_close")] + internal static extern int PcmClose(IntPtr pcm); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_hw_params_malloc")] + internal static unsafe extern int PcmHwParamsMalloc(out IntPtr hwparams); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_hw_params_free")] + internal static unsafe extern void PcmHwParamsFree(IntPtr hwparams); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_hw_params_test_access")] + internal static extern int PcmHwParamsTestAccess(IntPtr pcm, IntPtr hwparams, PCMAccess access); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_hw_params_any")] + internal static extern int PcmHwParamsAny(IntPtr pcm, IntPtr hwparams); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_hw_params_set_access")] + internal static extern int PcmHwParamsSetAccess(IntPtr pcm, IntPtr hwparams, PCMAccess access); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_hw_params_test_format")] + internal static extern int PcmHwParamsTestFormat(IntPtr pcm, IntPtr hwparams, PCMFormat format); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_hw_params_get_format")] + internal static extern int PcmHwParamsGetFormat(IntPtr pcm, out PCMFormat format); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_hw_params_set_format")] + internal static extern int PcmHwParamsSetFormat(IntPtr pcm, IntPtr hwparams, PCMFormat format); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_hw_params_set_rate")] + internal static extern int PcmHwParamsSetRate(IntPtr pcm, IntPtr hwparams, uint val, int dir); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_hw_params_set_channels")] + internal static extern int PcmHwParamsSetChannels(IntPtr pcm, IntPtr hwparams, uint val); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_hw_params_set_periods_near")] + internal static extern int PcmHwParamsSetPeriodsNear(IntPtr pcm, IntPtr hwparams, ref uint val, ref int dir); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_hw_params_set_buffer_size_near")] + internal static extern int PcmHwParamsSetBufferSizeNear(IntPtr pcm, IntPtr hwparams, ref ulong val); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_hw_params")] + internal static extern int PcmHwParams(IntPtr pcm, IntPtr hwparams); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_sw_params_malloc")] + internal static unsafe extern int PcmSwParamsMalloc(out IntPtr swparams); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_sw_params_free")] + internal static unsafe extern void PcmSwParamsFree(IntPtr swparams); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_sw_params_current")] + internal static unsafe extern int PcmSwParamsCurrent(IntPtr pcm, IntPtr swparams); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_sw_params")] + internal static unsafe extern int PcmSwParams(IntPtr pcm, IntPtr swparams); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_sw_params_set_avail_min")] + internal static extern int PcmSwParamsSetAvailMin(IntPtr pcm, IntPtr swparams, ulong val); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_writei")] + internal static unsafe extern int PcmWriteI(IntPtr pcm, byte[] buffer, ulong size); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_readi")] + internal static unsafe extern int PcmReadI(IntPtr pcm, byte[] buffer, ulong size); + [DllImport(AlsaLibrary, EntryPoint = "snd_async_add_pcm_handler")] + internal static extern int AsyncAddPcmHandler(out IntPtr handler, IntPtr pcm, PcmCallback callback, IntPtr private_data); + [DllImport(AlsaLibrary, EntryPoint = "snd_async_handler_get_pcm")] + internal static extern IntPtr AsyncHandlerGetPcm(IntPtr handler); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_start")] + internal static extern int PcmStart(IntPtr pcm); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_drop")] + internal static extern int PcmDrop(IntPtr pcm); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_drain")] + internal static extern int PcmDrain(IntPtr pcm); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_pause")] + internal static extern int PcmPause(IntPtr pcm, int enable); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_avail_update")] + internal static extern ulong PcmAvailUpdate(IntPtr pcm); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_prepare")] + internal static extern int PcmPrepare(IntPtr pcm); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_sw_params_set_start_threshold")] + internal static extern int PcmSwParamsSetStartThreshold(IntPtr pcm, IntPtr swparams, ulong val); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_hw_params_get_channels_max")] + internal static extern int PcmHwParamsGetChannelsMax(IntPtr hwparams, out uint val); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_hw_params_get_channels_min")] + internal static extern int PcmHwParamsGetChannelsMin(IntPtr hwparams, out uint val); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_hw_params_test_channels")] + internal static extern int PcmHwParamsTestChannels(IntPtr pcm, IntPtr hwparams, uint val); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_hw_params_test_rate")] + internal static extern int PcmHwParamsTestRate(IntPtr pcm, IntPtr hwparams, uint val, int dir); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_state")] + internal static extern PCMState PcmState(IntPtr pcm); + [DllImport(AlsaLibrary, EntryPoint = "snd_pcm_recover")] + internal static extern int PcmRecover(IntPtr pcm, int err, int silent); + private static string GetString(IntPtr ptr) + { + if (ptr == IntPtr.Zero) + { + return string.Empty; + } + else + { + return Marshal.PtrToStringAnsi(ptr); + } + } + internal static string CtlCardInfoGetID(IntPtr info) + { + return GetString(ctlCardInfoGetID(info)); + } + internal static string CtlCardInfoGetDriver(IntPtr info) + { + return GetString(ctlCardInfoGetDriver(info)); + } + internal static string CtlCardInfoGetName(IntPtr info) + { + return GetString(ctlCardInfoGetName(info)); + } + internal static string CtlCardInfoGetLongName(IntPtr info) + { + return GetString(ctlCardInfoGetLongName(info)); + } + internal static string CtlCardInfoGetMixerName(IntPtr info) + { + return GetString(ctlCardInfoGetMixerName(info)); + } + internal static string CtlCardInfoGetComponents(IntPtr info) + { + return GetString(ctlCardInfoGetComponents(info)); + } + internal static string PcmInfoGetID(IntPtr pcmInfo) + { + return GetString(pcmInfoGetID(pcmInfo)); + } + internal static string PcmInfoGetName(IntPtr pcmInfo) + { + return GetString(pcmInfoGetName(pcmInfo)); + } + internal static string PcmInfoGetSubdeviceName(IntPtr pcmInfo) + { + return GetString(pcmInfoGetSubdeviceName(pcmInfo)); + } + internal static string ErrorString(int error) + { + return GetString(strError(error)); + } + } +} \ No newline at end of file diff --git a/NAudio.Alsa/NAudio.Alsa.csproj b/NAudio.Alsa/NAudio.Alsa.csproj new file mode 100644 index 00000000..58af10c2 --- /dev/null +++ b/NAudio.Alsa/NAudio.Alsa.csproj @@ -0,0 +1,12 @@ + + + + + + + + netcoreapp2.0 + true + + + \ No newline at end of file diff --git a/NAudio.Core/Wave/SampleProviders/SampleToWaveProvider32.cs b/NAudio.Core/Wave/SampleProviders/SampleToWaveProvider32.cs new file mode 100644 index 00000000..71cb6644 --- /dev/null +++ b/NAudio.Core/Wave/SampleProviders/SampleToWaveProvider32.cs @@ -0,0 +1,83 @@ +using System; +using NAudio.Utils; + +namespace NAudio.Wave.SampleProviders +{ + /// + /// Converts a sample provider to 32 bit PCM, optionally clipping and adjusting volume along the way + /// + public class SampleToWaveProvider32 : IWaveProvider + { + private readonly ISampleProvider sourceProvider; + private readonly WaveFormat waveFormat; + private volatile float volume; + private float[] sourceBuffer; + + /// + /// Converts from an ISampleProvider (IEEE float) to a 16 bit PCM IWaveProvider. + /// Number of channels and sample rate remain unchanged. + /// + /// The input source provider + public SampleToWaveProvider32(ISampleProvider sourceProvider) + { + if (sourceProvider.WaveFormat.Encoding != WaveFormatEncoding.IeeeFloat) + throw new ArgumentException("Input source provider must be IEEE float", "sourceProvider"); + if (sourceProvider.WaveFormat.BitsPerSample != 32) + throw new ArgumentException("Input source provider must be 32 bit", "sourceProvider"); + + waveFormat = new WaveFormat(sourceProvider.WaveFormat.SampleRate, 32, sourceProvider.WaveFormat.Channels); + + this.sourceProvider = sourceProvider; + volume = 1.0f; + } + + /// + /// Reads bytes from this wave stream, clipping if necessary + /// + /// The destination buffer + /// Offset into the destination buffer + /// Number of bytes read + /// Number of bytes read. + public int Read(byte[] destBuffer, int offset, int numBytes) + { + var samplesRequired = numBytes / 4; + sourceBuffer = BufferHelpers.Ensure(sourceBuffer, samplesRequired); + var sourceSamples = sourceProvider.Read(sourceBuffer, 0, samplesRequired); + var destWaveBuffer = new WaveBuffer(destBuffer); + + int destOffset = offset / 4; + for (var sample = 0; sample < sourceSamples; sample++) + { + // adjust volume + var sample32 = sourceBuffer[sample] * volume; + // clip + if (sample32 > 1.0f) + sample32 = 1.0f; + if (sample32 < -1.0f) + sample32 = -1.0f; + + destWaveBuffer.IntBuffer[destOffset++] = (int) (sample32*2147483647); + } + + return sourceSamples * 4; + } + + /// + /// The Format of this IWaveProvider + /// + /// + public WaveFormat WaveFormat + { + get { return waveFormat; } + } + + /// + /// Volume of this channel. 1.0 = full scale, 0.0 to mute + /// + public float Volume + { + get { return volume; } + set { volume = value; } + } + } +} \ No newline at end of file diff --git a/NAudio/NAudio.csproj b/NAudio/NAudio.csproj index f204c07d..29a20d0f 100644 --- a/NAudio/NAudio.csproj +++ b/NAudio/NAudio.csproj @@ -24,7 +24,7 @@ - +