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