Merge branch 'master' into config-checks-ipv6
[mono.git] / mcs / class / System / System.Media / AudioDevice.cs
index bfc6c0c3f4c80fa6d6d8aae02412304b8ce809a4..2c54ef71a5ffa86a5a71b268b28d09bee8cbb559 100644 (file)
@@ -45,8 +45,12 @@ namespace Mono.Audio {
        internal
 #endif
        class AudioDevice {
-
+               protected uint chunk_size;
+               
                static AudioDevice TryAlsa (string name) {
+#if XAMMAC_4_5
+                       return null;
+#else
                        AudioDevice dev;
                        try {
                                dev = new AlsaDevice (name);
@@ -54,6 +58,7 @@ namespace Mono.Audio {
                        } catch {
                                return null;
                        }
+#endif
                }
 
                public static AudioDevice CreateDevice (string name) {
@@ -74,28 +79,105 @@ namespace Mono.Audio {
                        return num_frames;
                }
                
+               public virtual int XRunRecovery (int err) {
+                       return err;
+               }
+               
                public virtual void Wait () {
                }
+               
+               public uint ChunkSize {
+                       get { return chunk_size; }
+               }
        }
 
+#if !XAMMAC_4_5
        class AlsaDevice: AudioDevice, IDisposable {
                IntPtr handle;
-
-               [DllImport ("libasound.so.2")]
+               IntPtr hw_param;
+               IntPtr sw_param;
+               
+               [DllImport ("libasound")]
                static extern int snd_pcm_open (ref IntPtr handle, string pcm_name, int stream, int mode);
 
-               [DllImport ("libasound.so.2")]
+               [DllImport ("libasound")]
                static extern int snd_pcm_close (IntPtr handle);
 
-               [DllImport ("libasound.so.2")]
+               [DllImport ("libasound")]
                static extern int snd_pcm_drain (IntPtr handle);
 
-               [DllImport ("libasound.so.2")]
+               [DllImport ("libasound")]
                static extern int snd_pcm_writei (IntPtr handle, byte[] buf, int size);
 
-               [DllImport ("libasound.so.2")]
+               [DllImport ("libasound")]
                static extern int snd_pcm_set_params (IntPtr handle, int format, int access, int channels, int rate, int soft_resample, int latency);
 
+               [DllImport ("libasound")]
+               static extern int snd_pcm_state (IntPtr handle);
+
+               [DllImport ("libasound")]
+               static extern int snd_pcm_prepare (IntPtr handle);
+
+               [DllImport ("libasound")]
+               static extern int snd_pcm_hw_params (IntPtr handle, IntPtr param);
+
+               [DllImport ("libasound")]
+               static extern int snd_pcm_hw_params_malloc (ref IntPtr param);
+
+               [DllImport ("libasound")]
+               static extern void snd_pcm_hw_params_free (IntPtr param);
+
+               [DllImport ("libasound")]
+               static extern int snd_pcm_hw_params_any (IntPtr handle, IntPtr param);
+
+               [DllImport ("libasound")]
+               static extern int snd_pcm_hw_params_set_access (IntPtr handle, IntPtr param, int access);
+               
+               [DllImport ("libasound")]
+               static extern int snd_pcm_hw_params_set_format (IntPtr handle, IntPtr param, int format);
+
+               [DllImport ("libasound")]
+               static extern int snd_pcm_hw_params_set_channels (IntPtr handle, IntPtr param, uint channel);
+
+               [DllImport ("libasound")]
+               static extern int snd_pcm_hw_params_set_rate_near (IntPtr handle, IntPtr param, ref uint rate, ref int dir);
+
+               [DllImport ("libasound")]
+               static extern int snd_pcm_hw_params_set_period_time_near (IntPtr handle, IntPtr param, ref uint period, ref int dir);
+
+               [DllImport ("libasound")]
+               static extern int snd_pcm_hw_params_get_period_size (IntPtr param, ref uint period, ref int dir);
+
+               [DllImport ("libasound")]
+               static extern int snd_pcm_hw_params_set_buffer_size_near (IntPtr handle, IntPtr param, ref uint buff_size);
+
+               [DllImport ("libasound")]
+               static extern int snd_pcm_hw_params_get_buffer_time_max(IntPtr param, ref uint buffer_time, ref int dir);
+
+               [DllImport ("libasound")]
+               static extern int snd_pcm_hw_params_set_buffer_time_near(IntPtr handle, IntPtr param, ref uint BufferTime, ref int dir);
+
+               [DllImport ("libasound")]
+               static extern int snd_pcm_hw_params_get_buffer_size(IntPtr param, ref uint BufferSize);
+
+               [DllImport ("libasound")]
+               static extern int snd_pcm_sw_params (IntPtr handle, IntPtr param);
+
+               [DllImport ("libasound")]
+               static extern int snd_pcm_sw_params_malloc (ref IntPtr param);
+
+               [DllImport ("libasound")]
+               static extern void snd_pcm_sw_params_free (IntPtr param);
+
+               [DllImport ("libasound")]
+               static extern int snd_pcm_sw_params_current(IntPtr handle, IntPtr param);
+
+               [DllImport ("libasound")]
+               static extern int snd_pcm_sw_params_set_avail_min(IntPtr handle, IntPtr param, uint frames);
+
+               [DllImport ("libasound")]
+               static extern int snd_pcm_sw_params_set_start_threshold(IntPtr handle, IntPtr param, uint StartThreshold);
+
                public AlsaDevice (string name) {
                        if (name == null)
                                name = "default";
@@ -117,25 +199,132 @@ namespace Mono.Audio {
                        if (disposing) {
                                
                        }
+                       if (sw_param != IntPtr.Zero)
+                               snd_pcm_sw_params_free (sw_param);
+                       if (hw_param != IntPtr.Zero)
+                               snd_pcm_hw_params_free (hw_param);
                        if (handle != IntPtr.Zero)
                                snd_pcm_close (handle);
+                       sw_param = IntPtr.Zero;
+                       hw_param = IntPtr.Zero;
                        handle = IntPtr.Zero;
                }
 
                public override bool SetFormat (AudioFormat format, int channels, int rate) {
-                       int err = snd_pcm_set_params (handle, (int)format, 3, channels, rate, 1, 500000);
-                       return err == 0;
+                       int  alsa_err = -1;
+                       uint period_time = 0;
+                       uint period_size = 0;
+                       uint buffer_size = 0;
+                       uint buffer_time = 0;
+                       int dir = 0;
+                       uint sampling_rate = (uint)rate;
+
+                       // Alloc hw params structure
+                       alsa_err = snd_pcm_hw_params_malloc (ref hw_param);
+
+                       if (alsa_err == 0) {
+                               // get current hardware param
+                               snd_pcm_hw_params_any (handle, hw_param);
+
+                               // Set access to SND_PCM_ACCESS_RW_INTERLEAVED
+                               snd_pcm_hw_params_set_access (handle, hw_param, 3);
+                               // Set format to the file's format
+                               snd_pcm_hw_params_set_format (handle, hw_param, (int)format);
+                               // Set channel to the file's channel number
+                               snd_pcm_hw_params_set_channels (handle, hw_param, (uint)channels);
+
+                               dir = 0;
+                               // Set the sampling rate to the closest value
+                               snd_pcm_hw_params_set_rate_near (handle, hw_param, ref sampling_rate, ref dir);
+
+                               dir = 0;
+                               // Get the maximum buffer time allowed by hardware
+                               snd_pcm_hw_params_get_buffer_time_max (hw_param, ref buffer_time, ref dir);
+                               // At least, max buffer time = 500ms
+                               if (buffer_time > 500000) 
+                                       buffer_time = 500000;
+                               // The optimum time for a period is the quarter of the buffer time
+                               if (buffer_time > 0)
+                                       period_time = buffer_time / 4;
+
+                               dir = 0;
+                               snd_pcm_hw_params_set_period_time_near (handle, hw_param, ref period_time, ref dir);
+
+                               dir = 0;
+                               snd_pcm_hw_params_set_buffer_time_near (handle, hw_param, ref buffer_time, ref dir);
+
+                               // Get the period size in byte
+                               snd_pcm_hw_params_get_period_size (hw_param, ref period_size, ref dir);
+                               // Set the chunk size to the periode size
+                               // a chunk is a piece of wave raw data send to alsa, data are played chunk by chunk !
+                               chunk_size = period_size;
+
+                               snd_pcm_hw_params_get_buffer_size (hw_param, ref buffer_size);
+
+                               // Apply hardware params
+                               snd_pcm_hw_params (handle, hw_param);
+
+
+                       } else {
+                               Console.WriteLine ("failed to alloc Alsa hw param struct");
+                       }
+
+                       alsa_err = snd_pcm_sw_params_malloc (ref sw_param);
+                       if (alsa_err == 0) {
+                               // get current software param
+                               snd_pcm_sw_params_current (handle, sw_param);
+
+                               // Alsa becomes ready when there is at least chunk_size bytes (i.e. period) in its ring buffer !
+                               snd_pcm_sw_params_set_avail_min(handle, sw_param, chunk_size);
+                               // Alsa starts playing when there is buffer_size (i.e. the buffer is full) bytes in its ring buffer
+                               snd_pcm_sw_params_set_start_threshold(handle, sw_param, buffer_size);
+
+                               // apply software param
+                               snd_pcm_sw_params(handle, sw_param);
+                       } else {
+                               Console.WriteLine ("failed to alloc Alsa sw param struct");
+                       }
+
+                       if (hw_param != IntPtr.Zero) {
+                               snd_pcm_hw_params_free (hw_param);  // free hw params
+                               hw_param = IntPtr.Zero;
+                       }
+                       if (sw_param != IntPtr.Zero) {
+                               snd_pcm_sw_params_free (sw_param);  // free sw params
+                               sw_param = IntPtr.Zero;
+                       }
+
+                       return alsa_err == 0;
                }
 
                public override int PlaySample (byte[] buffer, int num_frames) {
-                       int frames = snd_pcm_writei (handle, buffer, num_frames);
+                       int frames;
+
+                       do {
+                               frames = snd_pcm_writei (handle, buffer, num_frames);
+                               if (frames < 0)
+                                       XRunRecovery(frames);
+                       }while (frames < 0);
+
                        return frames;
                }
                
+               public override int XRunRecovery (int err)
+               {
+                       int alsa_err = 0;
+                       
+                       // when alsa ring buffer UnderRun, snd_pcm_writei return -EPIPE (-32)
+                       if (-32 == err) {
+                               alsa_err = snd_pcm_prepare (handle);
+                       }
+                       return alsa_err;
+               }
+               
                public override void Wait () {
                        snd_pcm_drain (handle);
                }
        }
+#endif
 
 }