1 
2 using System;
3 using System.IO;
4 using System.Runtime.InteropServices;
5 
6 namespace Mono.Audio {
7 
8 	/* these are the values used by alsa */
9 #if PUBLIC_API
10 	public
11 #else
12 	internal
13 #endif
14 	enum AudioFormat {
15 		S8,
16 		U8,
17 		S16_LE,
18 		S16_BE,
19 		U16_LE,
20 		U16_BE,
21 		S24_LE,
22 		S24_BE,
23 		U24_LE,
24 		U24_BE,
25 		S32_LE,
26 		S32_BE,
27 		U32_LE,
28 		U32_BE,
29 		FLOAT_LE,
30 		FLOAT_BE,
31 		FLOAT64_LE,
32 		FLOAT64_BE,
33 		IEC958_SUBFRAME_LE,
34 		IEC958_SUBFRAME_BE,
35 		MU_LAW,
36 		A_LAW,
37 		IMA_ADPCM,
38 		MPEG,
39 		GSM
40 	}
41 
42 #if PUBLIC_API
43 	public
44 #else
45 	internal
46 #endif
47 	class AudioDevice {
48 		protected uint chunk_size;
49 
TryAlsa(string name)50 		static AudioDevice TryAlsa (string name) {
51 #if XAMMAC_4_5
52 			return null;
53 #else
54 			AudioDevice dev;
55 			try {
56 				dev = new AlsaDevice (name);
57 				return dev;
58 			} catch {
59 				return null;
60 			}
61 #endif
62 		}
63 
CreateDevice(string name)64 		public static AudioDevice CreateDevice (string name) {
65 			AudioDevice dev;
66 
67 			dev = TryAlsa (name);
68 			/* if no option is found, return a silent device */
69 			if (dev == null)
70 				dev = new AudioDevice ();
71 			return dev;
72 		}
73 
SetFormat(AudioFormat format, int channels, int rate)74 		public virtual bool SetFormat (AudioFormat format, int channels, int rate) {
75 			return true;
76 		}
77 
PlaySample(byte[] buffer, int num_frames)78 		public virtual int PlaySample (byte[] buffer, int num_frames) {
79 			return num_frames;
80 		}
81 
XRunRecovery(int err)82 		public virtual int XRunRecovery (int err) {
83 			return err;
84 		}
85 
Wait()86 		public virtual void Wait () {
87 		}
88 
89 		public uint ChunkSize {
90 			get { return chunk_size; }
91 		}
92 	}
93 
94 #if !XAMMAC_4_5
95 	class AlsaDevice: AudioDevice, IDisposable {
96 		IntPtr handle;
97 		IntPtr hw_param;
98 		IntPtr sw_param;
99 
100 		[DllImport ("libasound")]
snd_pcm_open(ref IntPtr handle, string pcm_name, int stream, int mode)101 		static extern int snd_pcm_open (ref IntPtr handle, string pcm_name, int stream, int mode);
102 
103 		[DllImport ("libasound")]
snd_pcm_close(IntPtr handle)104 		static extern int snd_pcm_close (IntPtr handle);
105 
106 		[DllImport ("libasound")]
snd_pcm_drain(IntPtr handle)107 		static extern int snd_pcm_drain (IntPtr handle);
108 
109 		[DllImport ("libasound")]
snd_pcm_writei(IntPtr handle, byte[] buf, int size)110 		static extern int snd_pcm_writei (IntPtr handle, byte[] buf, int size);
111 
112 		[DllImport ("libasound")]
snd_pcm_set_params(IntPtr handle, int format, int access, int channels, int rate, int soft_resample, int latency)113 		static extern int snd_pcm_set_params (IntPtr handle, int format, int access, int channels, int rate, int soft_resample, int latency);
114 
115 		[DllImport ("libasound")]
snd_pcm_state(IntPtr handle)116 		static extern int snd_pcm_state (IntPtr handle);
117 
118 		[DllImport ("libasound")]
snd_pcm_prepare(IntPtr handle)119 		static extern int snd_pcm_prepare (IntPtr handle);
120 
121 		[DllImport ("libasound")]
snd_pcm_hw_params(IntPtr handle, IntPtr param)122 		static extern int snd_pcm_hw_params (IntPtr handle, IntPtr param);
123 
124 		[DllImport ("libasound")]
snd_pcm_hw_params_malloc(ref IntPtr param)125 		static extern int snd_pcm_hw_params_malloc (ref IntPtr param);
126 
127 		[DllImport ("libasound")]
snd_pcm_hw_params_free(IntPtr param)128 		static extern void snd_pcm_hw_params_free (IntPtr param);
129 
130 		[DllImport ("libasound")]
snd_pcm_hw_params_any(IntPtr handle, IntPtr param)131 		static extern int snd_pcm_hw_params_any (IntPtr handle, IntPtr param);
132 
133 		[DllImport ("libasound")]
snd_pcm_hw_params_set_access(IntPtr handle, IntPtr param, int access)134 		static extern int snd_pcm_hw_params_set_access (IntPtr handle, IntPtr param, int access);
135 
136 		[DllImport ("libasound")]
snd_pcm_hw_params_set_format(IntPtr handle, IntPtr param, int format)137 		static extern int snd_pcm_hw_params_set_format (IntPtr handle, IntPtr param, int format);
138 
139 		[DllImport ("libasound")]
snd_pcm_hw_params_set_channels(IntPtr handle, IntPtr param, uint channel)140 		static extern int snd_pcm_hw_params_set_channels (IntPtr handle, IntPtr param, uint channel);
141 
142 		[DllImport ("libasound")]
snd_pcm_hw_params_set_rate_near(IntPtr handle, IntPtr param, ref uint rate, ref int dir)143 		static extern int snd_pcm_hw_params_set_rate_near (IntPtr handle, IntPtr param, ref uint rate, ref int dir);
144 
145 		[DllImport ("libasound")]
snd_pcm_hw_params_set_period_time_near(IntPtr handle, IntPtr param, ref uint period, ref int dir)146 		static extern int snd_pcm_hw_params_set_period_time_near (IntPtr handle, IntPtr param, ref uint period, ref int dir);
147 
148 		[DllImport ("libasound")]
snd_pcm_hw_params_get_period_size(IntPtr param, ref uint period, ref int dir)149 		static extern int snd_pcm_hw_params_get_period_size (IntPtr param, ref uint period, ref int dir);
150 
151 		[DllImport ("libasound")]
snd_pcm_hw_params_set_buffer_size_near(IntPtr handle, IntPtr param, ref uint buff_size)152 		static extern int snd_pcm_hw_params_set_buffer_size_near (IntPtr handle, IntPtr param, ref uint buff_size);
153 
154 		[DllImport ("libasound")]
snd_pcm_hw_params_get_buffer_time_max(IntPtr param, ref uint buffer_time, ref int dir)155 		static extern int snd_pcm_hw_params_get_buffer_time_max(IntPtr param, ref uint buffer_time, ref int dir);
156 
157 		[DllImport ("libasound")]
snd_pcm_hw_params_set_buffer_time_near(IntPtr handle, IntPtr param, ref uint BufferTime, ref int dir)158 		static extern int snd_pcm_hw_params_set_buffer_time_near(IntPtr handle, IntPtr param, ref uint BufferTime, ref int dir);
159 
160 		[DllImport ("libasound")]
snd_pcm_hw_params_get_buffer_size(IntPtr param, ref uint BufferSize)161 		static extern int snd_pcm_hw_params_get_buffer_size(IntPtr param, ref uint BufferSize);
162 
163 		[DllImport ("libasound")]
snd_pcm_sw_params(IntPtr handle, IntPtr param)164 		static extern int snd_pcm_sw_params (IntPtr handle, IntPtr param);
165 
166 		[DllImport ("libasound")]
snd_pcm_sw_params_malloc(ref IntPtr param)167 		static extern int snd_pcm_sw_params_malloc (ref IntPtr param);
168 
169 		[DllImport ("libasound")]
snd_pcm_sw_params_free(IntPtr param)170 		static extern void snd_pcm_sw_params_free (IntPtr param);
171 
172 		[DllImport ("libasound")]
snd_pcm_sw_params_current(IntPtr handle, IntPtr param)173 		static extern int snd_pcm_sw_params_current(IntPtr handle, IntPtr param);
174 
175 		[DllImport ("libasound")]
snd_pcm_sw_params_set_avail_min(IntPtr handle, IntPtr param, uint frames)176 		static extern int snd_pcm_sw_params_set_avail_min(IntPtr handle, IntPtr param, uint frames);
177 
178 		[DllImport ("libasound")]
snd_pcm_sw_params_set_start_threshold(IntPtr handle, IntPtr param, uint StartThreshold)179 		static extern int snd_pcm_sw_params_set_start_threshold(IntPtr handle, IntPtr param, uint StartThreshold);
180 
AlsaDevice(string name)181 		public AlsaDevice (string name) {
182 			if (name == null)
183 				name = "default";
184 			int err = snd_pcm_open (ref handle, name, 0, 0);
185 			if (err < 0)
186 				throw new Exception ("no open " + err);
187 		}
188 
~AlsaDevice()189 		~AlsaDevice () {
190 			Dispose (false);
191 		}
192 
Dispose()193 		public void Dispose () {
194 			Dispose (true);
195 			GC.SuppressFinalize (this);
196 		}
197 
Dispose(bool disposing)198 		protected virtual void Dispose (bool disposing) {
199 			if (disposing) {
200 
201 			}
202 			if (sw_param != IntPtr.Zero)
203 				snd_pcm_sw_params_free (sw_param);
204 			if (hw_param != IntPtr.Zero)
205 				snd_pcm_hw_params_free (hw_param);
206 			if (handle != IntPtr.Zero)
207 				snd_pcm_close (handle);
208 			sw_param = IntPtr.Zero;
209 			hw_param = IntPtr.Zero;
210 			handle = IntPtr.Zero;
211 		}
212 
SetFormat(AudioFormat format, int channels, int rate)213 		public override bool SetFormat (AudioFormat format, int channels, int rate) {
214 			int  alsa_err = -1;
215 			uint period_time = 0;
216 			uint period_size = 0;
217 			uint buffer_size = 0;
218 			uint buffer_time = 0;
219 			int dir = 0;
220 			uint sampling_rate = (uint)rate;
221 
222 			// Alloc hw params structure
223 			alsa_err = snd_pcm_hw_params_malloc (ref hw_param);
224 
225 			if (alsa_err == 0) {
226 				// get current hardware param
227 				snd_pcm_hw_params_any (handle, hw_param);
228 
229 				// Set access to SND_PCM_ACCESS_RW_INTERLEAVED
230 				snd_pcm_hw_params_set_access (handle, hw_param, 3);
231 				// Set format to the file's format
232 				snd_pcm_hw_params_set_format (handle, hw_param, (int)format);
233 				// Set channel to the file's channel number
234 				snd_pcm_hw_params_set_channels (handle, hw_param, (uint)channels);
235 
236 				dir = 0;
237 				// Set the sampling rate to the closest value
238 				snd_pcm_hw_params_set_rate_near (handle, hw_param, ref sampling_rate, ref dir);
239 
240 				dir = 0;
241 				// Get the maximum buffer time allowed by hardware
242 				snd_pcm_hw_params_get_buffer_time_max (hw_param, ref buffer_time, ref dir);
243 				// At least, max buffer time = 500ms
244 				if (buffer_time > 500000)
245 					buffer_time = 500000;
246 				// The optimum time for a period is the quarter of the buffer time
247 				if (buffer_time > 0)
248 					period_time = buffer_time / 4;
249 
250 				dir = 0;
251 				snd_pcm_hw_params_set_period_time_near (handle, hw_param, ref period_time, ref dir);
252 
253 				dir = 0;
254 				snd_pcm_hw_params_set_buffer_time_near (handle, hw_param, ref buffer_time, ref dir);
255 
256 				// Get the period size in byte
257 				snd_pcm_hw_params_get_period_size (hw_param, ref period_size, ref dir);
258 				// Set the chunk size to the periode size
259 				// a chunk is a piece of wave raw data send to alsa, data are played chunk by chunk !
260 				chunk_size = period_size;
261 
262 				snd_pcm_hw_params_get_buffer_size (hw_param, ref buffer_size);
263 
264 				// Apply hardware params
265 				snd_pcm_hw_params (handle, hw_param);
266 
267 
268 			} else {
269 				Console.WriteLine ("failed to alloc Alsa hw param struct");
270 			}
271 
272 			alsa_err = snd_pcm_sw_params_malloc (ref sw_param);
273 			if (alsa_err == 0) {
274 				// get current software param
275 				snd_pcm_sw_params_current (handle, sw_param);
276 
277 				// Alsa becomes ready when there is at least chunk_size bytes (i.e. period) in its ring buffer !
278 				snd_pcm_sw_params_set_avail_min(handle, sw_param, chunk_size);
279 				// Alsa starts playing when there is buffer_size (i.e. the buffer is full) bytes in its ring buffer
280 				snd_pcm_sw_params_set_start_threshold(handle, sw_param, buffer_size);
281 
282 				// apply software param
283 				snd_pcm_sw_params(handle, sw_param);
284 			} else {
285 				Console.WriteLine ("failed to alloc Alsa sw param struct");
286 			}
287 
288 			if (hw_param != IntPtr.Zero) {
289 				snd_pcm_hw_params_free (hw_param);  // free hw params
290 				hw_param = IntPtr.Zero;
291 			}
292 			if (sw_param != IntPtr.Zero) {
293 				snd_pcm_sw_params_free (sw_param);  // free sw params
294 				sw_param = IntPtr.Zero;
295 			}
296 
297 			return alsa_err == 0;
298 		}
299 
PlaySample(byte[] buffer, int num_frames)300 		public override int PlaySample (byte[] buffer, int num_frames) {
301 			int frames;
302 
303 			do {
304 				frames = snd_pcm_writei (handle, buffer, num_frames);
305 				if (frames < 0)
306 					XRunRecovery(frames);
307 			}while (frames < 0);
308 
309 			return frames;
310 		}
311 
XRunRecovery(int err)312 		public override int XRunRecovery (int err)
313 		{
314 			int alsa_err = 0;
315 
316 			// when alsa ring buffer UnderRun, snd_pcm_writei return -EPIPE (-32)
317 			if (-32 == err) {
318 				alsa_err = snd_pcm_prepare (handle);
319 			}
320 			return alsa_err;
321 		}
322 
Wait()323 		public override void Wait () {
324 			snd_pcm_drain (handle);
325 		}
326 	}
327 #endif
328 
329 }
330 
331