1 module iota.audio.alsa; 2 3 version (linux): 4 5 package import iota.audio.backend.linux; 6 7 import iota.audio.device; 8 import iota.audio.output; 9 10 import core.time; 11 12 import core.stdc.stdlib; 13 14 import core.thread; 15 16 /// Name of all devices 17 package static char*[] devicenames; 18 /// The last ALSA specific error code 19 public static int lastErrorCode; 20 21 /// Automatic cleanup. 22 shared static ~this() { 23 flushNames(); 24 } 25 /** 26 * Flushes all device names automatically. 27 */ 28 package void flushNames() { 29 foreach (key ; devicenames) 30 free(key); 31 } 32 /** 33 * Returns the error string from an error code. 34 * Params: 35 * errCode = 36 */ 37 public string getErrorString(int errCode) { 38 import std.string : fromStringz; 39 return fromStringz(snd_strerror(errCode)).idup; 40 } 41 /** 42 * Implements an ALSA device. 43 */ 44 public class ALSADevice : AudioDevice { 45 package snd_pcm_t* pcmHandle; 46 protected snd_pcm_hw_params_t* pcmParams; 47 /// Contains ALSA-related error codes. 48 public int alsaerrCode; 49 package this (snd_pcm_t* pcmHandle) { 50 this.pcmHandle = pcmHandle; 51 snd_pcm_hw_params_malloc(&pcmParams); 52 snd_pcm_hw_params_any(pcmHandle, pcmParams); 53 uint val; 54 snd_pcm_hw_params_get_channels_max(pcmParams, &val); 55 this._nOfOutputs = cast(short)val; 56 this._nOfInputs = -1; 57 } 58 ~this() { 59 snd_pcm_close(pcmHandle); 60 snd_pcm_hw_params_free(pcmParams); 61 62 } 63 64 override public AudioSpecs requestSpecs(AudioSpecs reqSpecs, int flags = 0) { 65 reqSpecs.mirrorBufferSizes(); 66 alsaerrCode = snd_pcm_hw_params_set_access(pcmHandle, pcmParams, snd_pcm_access_t.SND_PCM_ACCESS_RW_INTERLEAVED); 67 if (alsaerrCode) {errCode = StreamInitializationStatus.UnsupportedFormat; return AudioSpecs.init;} 68 switch (reqSpecs.format.bits) { 69 case 0x08: 70 switch (reqSpecs.format.flags & SampleFormat.Flag.Type_Test) { 71 case SampleFormat.Flag.Type_Unsigned: 72 alsaerrCode = snd_pcm_hw_params_set_format(pcmHandle, pcmParams, snd_pcm_format.SND_PCM_FORMAT_U8); 73 break; 74 case SampleFormat.Flag.Type_Signed: 75 alsaerrCode = snd_pcm_hw_params_set_format(pcmHandle, pcmParams, snd_pcm_format.SND_PCM_FORMAT_S8); 76 break; 77 default: 78 errCode = StreamInitializationStatus.UnsupportedFormat; return AudioSpecs.init; 79 } 80 break; 81 case 0x10: 82 switch (reqSpecs.format.flags & SampleFormat.Flag.Type_Test) { 83 case SampleFormat.Flag.Type_Unsigned: 84 alsaerrCode = snd_pcm_hw_params_set_format(pcmHandle, pcmParams, snd_pcm_format.SND_PCM_FORMAT_U16_LE); 85 break; 86 case SampleFormat.Flag.Type_Signed: 87 alsaerrCode = snd_pcm_hw_params_set_format(pcmHandle, pcmParams, snd_pcm_format.SND_PCM_FORMAT_S16_LE); 88 break; 89 default: 90 errCode = StreamInitializationStatus.UnsupportedFormat; return AudioSpecs.init; 91 } 92 break; 93 case 0x18: 94 switch (reqSpecs.format.flags & SampleFormat.Flag.Type_Test) { 95 case SampleFormat.Flag.Type_Unsigned: 96 alsaerrCode = snd_pcm_hw_params_set_format(pcmHandle, pcmParams, snd_pcm_format.SND_PCM_FORMAT_U24_LE); 97 break; 98 case SampleFormat.Flag.Type_Signed: 99 alsaerrCode = snd_pcm_hw_params_set_format(pcmHandle, pcmParams, snd_pcm_format.SND_PCM_FORMAT_S24_LE); 100 break; 101 default: 102 errCode = StreamInitializationStatus.UnsupportedFormat; return AudioSpecs.init; 103 } 104 break; 105 case 0x20: 106 switch (reqSpecs.format.flags & SampleFormat.Flag.Type_Test) { 107 case SampleFormat.Flag.Type_Float: 108 alsaerrCode = snd_pcm_hw_params_set_format(pcmHandle, pcmParams, snd_pcm_format.SND_PCM_FORMAT_FLOAT_LE); 109 break; 110 case SampleFormat.Flag.Type_Unsigned: 111 alsaerrCode = snd_pcm_hw_params_set_format(pcmHandle, pcmParams, snd_pcm_format.SND_PCM_FORMAT_U32_LE); 112 break; 113 case SampleFormat.Flag.Type_Signed: 114 alsaerrCode = snd_pcm_hw_params_set_format(pcmHandle, pcmParams, snd_pcm_format.SND_PCM_FORMAT_S32_LE); 115 break; 116 default: 117 errCode = StreamInitializationStatus.UnsupportedFormat; return AudioSpecs.init; 118 } 119 break; 120 default: 121 errCode = StreamInitializationStatus.UnsupportedFormat; return AudioSpecs.init; 122 } 123 if (alsaerrCode) {errCode = StreamInitializationStatus.UnsupportedFormat; return AudioSpecs.init;} 124 alsaerrCode = snd_pcm_hw_params_set_channels(pcmHandle, pcmParams, reqSpecs.outputChannels); 125 if (alsaerrCode) {errCode = StreamInitializationStatus.UnsupportedFormat; return AudioSpecs.init;} 126 //snd_pcm_hw_params_set_access(pcmHandle, pcmParams, snd_pcm_access_t.SND_PCM_ACCESS_RW_INTERLEAVED); 127 int something = 0; 128 alsaerrCode = snd_pcm_hw_params_set_rate_near(pcmHandle, pcmParams, cast(uint*)&reqSpecs.sampleRate, &something); 129 if (alsaerrCode) {errCode = StreamInitializationStatus.UnsupportedFormat; return AudioSpecs.init;} 130 uint periods = (reqSpecs.bits() * reqSpecs.outputChannels) / 8; 131 alsaerrCode = snd_pcm_hw_params_set_periods_near(pcmHandle, pcmParams, &periods, 0); 132 if (alsaerrCode) {errCode = StreamInitializationStatus.UnsupportedFormat; return AudioSpecs.init;} 133 snd_pcm_uframes_t bufferlen = reqSpecs.bufferSize_slmp * periods; 134 alsaerrCode = snd_pcm_hw_params_set_buffer_size_near(pcmHandle, pcmParams, &bufferlen); 135 if (alsaerrCode) {errCode = StreamInitializationStatus.UnsupportedFormat; return AudioSpecs.init;} 136 reqSpecs.bufferSize_slmp = cast(uint)(bufferlen / periods); 137 reqSpecs.bufferSize_time = Duration.init; 138 reqSpecs.mirrorBufferSizes(); 139 alsaerrCode = snd_pcm_hw_params(pcmHandle, pcmParams); 140 if (alsaerrCode) {errCode = StreamInitializationStatus.UnsupportedFormat; return AudioSpecs.init;} 141 // Set up software parameters 142 snd_pcm_sw_params_t* swparams; 143 alsaerrCode = snd_pcm_sw_params_malloc(&swparams); 144 scope (exit) 145 snd_pcm_sw_params_free(swparams); 146 if (alsaerrCode) {errCode = StreamInitializationStatus.OutOfMemory; return AudioSpecs.init;} 147 alsaerrCode = snd_pcm_sw_params_current(pcmHandle, swparams); 148 if (alsaerrCode) {errCode = StreamInitializationStatus.OutOfMemory; return AudioSpecs.init;} 149 alsaerrCode = snd_pcm_sw_params_set_avail_min(pcmHandle, swparams, reqSpecs.bufferSize_slmp); 150 if (alsaerrCode) {errCode = StreamInitializationStatus.UnsupportedFormat; return AudioSpecs.init;} 151 alsaerrCode = snd_pcm_sw_params_set_start_threshold(pcmHandle, swparams, 0); 152 if (alsaerrCode) {errCode = StreamInitializationStatus.UnsupportedFormat; return AudioSpecs.init;} 153 alsaerrCode = snd_pcm_sw_params(pcmHandle, swparams); 154 if (alsaerrCode) {errCode = StreamInitializationStatus.UnsupportedFormat; return AudioSpecs.init;} 155 //snd_pcm_nonblock(pcmHandle, 0); 156 return _specs = reqSpecs; 157 } 158 159 override public OutputStream createOutputStream() { 160 alsaerrCode = snd_pcm_prepare(pcmHandle); 161 if (alsaerrCode) { 162 errCode = StreamInitializationStatus.Unknown; 163 return null; // TODO: Implement better error-handling 164 } else { 165 return new ALSAOutStream(this); 166 } 167 } 168 } 169 /** 170 * Implements an ALSA output stream. 171 */ 172 public class ALSAOutStream : OutputStream { 173 /// Due to how ALSA works, the pcm handle is accessed from there directly, also keeps a reference of it to avoid 174 /// calling the destructor. 175 protected ALSADevice dev; 176 /// Buffer for outputting audio data. 177 protected ubyte[] buffer; 178 /// Contains buffer size in samples. 179 protected uint bufferSize; 180 /// Contains the last ALSA specific error code. 181 public int alsaerrCode; 182 183 package this (ALSADevice dev) { 184 this.dev = dev; 185 buffer.length = (dev.specs().bufferSize_slmp * dev.specs().bits * dev.specs().outputChannels) / 8; 186 this.bufferSize = dev.specs().bufferSize_slmp; 187 } 188 ~this() { 189 190 } 191 protected void audioThread() @nogc nothrow { 192 snd_pcm_sframes_t currBufferSize; 193 while (statusCode & StatusFlags.IsRunning) { 194 callback_buffer(buffer); 195 do { 196 currBufferSize = snd_pcm_writei(dev.pcmHandle, cast(const(void*))buffer.ptr, bufferSize); 197 if (currBufferSize < 0) { 198 errCode = StreamRuntimeStatus.Unknown; 199 return; 200 } 201 } while (currBufferSize < bufferSize && statusCode & StatusFlags.IsRunning); 202 alsaerrCode = snd_pcm_wait(dev.pcmHandle, 1000); 203 if (!alsaerrCode) { 204 statusCode |= StatusFlags.BufferUnderrun; 205 } 206 } 207 } 208 override public int runAudioThread() @nogc nothrow { 209 if (callback_buffer is null) return errCode = StreamRuntimeStatus.BufferCallbackNotSet; 210 alsaerrCode = snd_pcm_start(dev.pcmHandle); 211 if (alsaerrCode) { 212 return errCode = StreamRuntimeStatus.Unknown; 213 } 214 statusCode |= StatusFlags.IsRunning; 215 _threadID = createLowLevelThread(&audioThread); 216 return errCode = StreamRuntimeStatus.AllOk; 217 } 218 219 override public int suspendAudioThread() @nogc nothrow { 220 alsaerrCode = snd_pcm_drain(dev.pcmHandle); 221 statusCode &= ~StatusFlags.IsRunning; 222 return errCode; 223 } 224 }