Skip to content
Open

Alsa #1182

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
dc25821
add alsa.net project
k2fc Sep 5, 2024
bd7aaf3
alsa playback making sound!
k2fc Sep 8, 2024
664b686
remove excessive printing
k2fc Sep 8, 2024
2dd78f7
Make 32-bit PCM work
k2fc Sep 8, 2024
c1880bf
use built-in intbuffer:
k2fc Sep 8, 2024
438faee
add pausing
k2fc Sep 9, 2024
e401595
support cards which do not do async callback
k2fc Sep 9, 2024
9177547
rename AlsaDriverExt to AlsaInterop
k2fc Sep 10, 2024
db7db1a
force synchronous for now
k2fc Sep 10, 2024
bd66f62
make pause not explode
k2fc Sep 11, 2024
fb4e9a8
fix async playback
k2fc Sep 11, 2024
ddfc0db
Removed submodule alsa.net
k2fc Sep 11, 2024
9f8e053
refactor hardware param setting
k2fc Sep 12, 2024
0674934
remove unused interop methods
k2fc Sep 12, 2024
df0e1bf
add missing attribute line
k2fc Sep 12, 2024
2dd90c5
begin refactor of software params setting
k2fc Sep 12, 2024
afd2bbf
remove unneeded pointer
k2fc Sep 12, 2024
6e5da15
refactor set format
k2fc Sep 12, 2024
dd18179
create beginning of AlsaIn object
k2fc Sep 13, 2024
1b0d19f
remove unused reference
k2fc Sep 13, 2024
d970376
move buffers to abstract class
k2fc Sep 13, 2024
f2b485e
remove unused reference
k2fc Sep 13, 2024
cf24e96
refactor allocation and freeing of SwParams
k2fc Sep 13, 2024
5328a04
fix playback pipe
k2fc Sep 13, 2024
c3ac424
Revert "fix playback pipe"
k2fc Sep 13, 2024
acfdc3f
fix broken pipe
k2fc Sep 13, 2024
22d6fae
enumerate valid wave formats for pcm
k2fc Sep 13, 2024
0108a7d
add methods for testing waveformats
k2fc Sep 13, 2024
8e017d0
add AlsaException
k2fc Sep 14, 2024
9eb62a2
remove unnecesary assignment
k2fc Sep 14, 2024
b0d97bd
make AlsaException serializable
k2fc Sep 14, 2024
4d6487c
throw exceptions if failed
k2fc Sep 14, 2024
d08df92
move buffers to abstract class
k2fc Sep 14, 2024
8a49cc7
refactor buffer init
k2fc Sep 14, 2024
265ef1b
allow changing number of buffers prior to init
k2fc Sep 14, 2024
778d1c2
beginning of recording code
k2fc Sep 14, 2024
c3c3b78
recording is working
k2fc Sep 15, 2024
64d7a65
remove unused struct
k2fc Sep 16, 2024
8ca84cc
add state and attempt recovery
k2fc Sep 16, 2024
e18216d
don't include WinForms on Linux
k2fc Sep 16, 2024
9167d04
attempt to explain myself
k2fc Sep 16, 2024
469353f
add dummy Directory.Build.props for Nerdbank Gitversioning
k2fc Apr 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added .gitmodules
Empty file.
3 changes: 3 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<Project>
<!-- Only here so that the default Directory.Build.props will not be used. -->
</Project>
93 changes: 93 additions & 0 deletions NAudio.Alsa/AlsaCard.cs
Original file line number Diff line number Diff line change
@@ -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<AlsaOut> OutputDevices { get; private set; } = new List<AlsaOut>();
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();
}
}
}
62 changes: 62 additions & 0 deletions NAudio.Alsa/AlsaDriver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
namespace NAudio.Wave.Alsa
{
public class AlsaDriver
{
private IntPtr _playbackPcm;
private List<AlsaCard> _cards = new List<AlsaCard>();
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;
}
}
}
13 changes: 13 additions & 0 deletions NAudio.Alsa/AlsaException.cs
Original file line number Diff line number Diff line change
@@ -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)))
{
}
}
176 changes: 176 additions & 0 deletions NAudio.Alsa/AlsaIn.cs
Original file line number Diff line number Diff line change
@@ -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<WaveInEventArgs> DataAvailable;
public event EventHandler<StoppedEventArgs> 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));
}
}
}
Loading