1 /*****************************************************************************\
2      Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
3                 This file is licensed under the Snes9x License.
4    For further information, consult the LICENSE file in the root directory.
5 \*****************************************************************************/
6 
7 #include "gtk_sound_driver_alsa.h"
8 #include "gtk_s9x.h"
9 
10 #include <fcntl.h>
11 #include <sys/ioctl.h>
12 #include <sys/time.h>
13 
alsa_samples_available(void * data)14 static void alsa_samples_available(void *data)
15 {
16     ((S9xAlsaSoundDriver *)data)->samples_available();
17 }
18 
S9xAlsaSoundDriver()19 S9xAlsaSoundDriver::S9xAlsaSoundDriver()
20 {
21     pcm = NULL;
22     sound_buffer = NULL;
23     sound_buffer_size = 0;
24 }
25 
init()26 void S9xAlsaSoundDriver::init()
27 {
28 }
29 
terminate()30 void S9xAlsaSoundDriver::terminate()
31 {
32     stop();
33 
34     S9xSetSamplesAvailableCallback(NULL, NULL);
35 
36     if (pcm)
37     {
38         snd_pcm_drain(pcm);
39         snd_pcm_close(pcm);
40         pcm = NULL;
41     }
42 
43     if (sound_buffer)
44     {
45         free(sound_buffer);
46         sound_buffer = NULL;
47     }
48 }
49 
start()50 void S9xAlsaSoundDriver::start()
51 {
52 }
53 
stop()54 void S9xAlsaSoundDriver::stop()
55 {
56 }
57 
open_device()58 bool S9xAlsaSoundDriver::open_device()
59 {
60     int err;
61     unsigned int periods = 8;
62     unsigned int buffer_size = gui_config->sound_buffer_size * 1000;
63     snd_pcm_sw_params_t *sw_params;
64     snd_pcm_hw_params_t *hw_params;
65     snd_pcm_uframes_t alsa_buffer_size, alsa_period_size;
66     unsigned int min = 0;
67     unsigned int max = 0;
68 
69     printf("ALSA sound driver initializing...\n");
70     printf("    --> (Device: default)...\n");
71 
72     err = snd_pcm_open(&pcm,
73                        "default",
74                        SND_PCM_STREAM_PLAYBACK,
75                        SND_PCM_NONBLOCK);
76 
77     if (err < 0)
78     {
79         goto fail;
80     }
81 
82     printf("    --> (16-bit Stereo, %dhz, %d ms)...\n",
83            Settings.SoundPlaybackRate,
84            gui_config->sound_buffer_size);
85 
86     snd_pcm_hw_params_alloca(&hw_params);
87     snd_pcm_hw_params_any(pcm, hw_params);
88     snd_pcm_hw_params_set_format(pcm, hw_params, SND_PCM_FORMAT_S16);
89     snd_pcm_hw_params_set_access(pcm, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
90     snd_pcm_hw_params_set_rate_resample(pcm, hw_params, 0);
91     snd_pcm_hw_params_set_channels(pcm, hw_params, 2);
92 
93     snd_pcm_hw_params_get_rate_min(hw_params, &min, NULL);
94     snd_pcm_hw_params_get_rate_max(hw_params, &max, NULL);
95     printf("    --> Available rates: %d to %d\n", min, max);
96     if (Settings.SoundPlaybackRate > max && Settings.SoundPlaybackRate < min)
97     {
98         printf("        Rate %d not available. Using %d instead.\n", Settings.SoundPlaybackRate, max);
99         Settings.SoundPlaybackRate = max;
100     }
101     snd_pcm_hw_params_set_rate_near(pcm, hw_params, &Settings.SoundPlaybackRate, NULL);
102 
103     snd_pcm_hw_params_get_buffer_time_min(hw_params, &min, NULL);
104     snd_pcm_hw_params_get_buffer_time_max(hw_params, &max, NULL);
105     printf("    --> Available buffer sizes: %dms to %dms\n", min / 1000, max / 1000);
106     if (buffer_size < min && buffer_size > max)
107     {
108         printf("        Buffer size %dms not available. Using %d instead.\n", buffer_size / 1000, (min + max) / 2000);
109         buffer_size = (min + max) / 2;
110     }
111     snd_pcm_hw_params_set_buffer_time_near(pcm, hw_params, &buffer_size, NULL);
112 
113     snd_pcm_hw_params_get_periods_min(hw_params, &min, NULL);
114     snd_pcm_hw_params_get_periods_max(hw_params, &max, NULL);
115     printf("    --> Period ranges: %d to %d blocks\n", min, max);
116     if (periods > max)
117     {
118         periods = max;
119     }
120     snd_pcm_hw_params_set_periods_near(pcm, hw_params, &periods, NULL);
121 
122     if ((err = snd_pcm_hw_params(pcm, hw_params)) < 0)
123     {
124         printf("        Hardware parameter set failed.\n");
125         goto close_fail;
126     }
127 
128     snd_pcm_sw_params_alloca(&sw_params);
129     snd_pcm_sw_params_current(pcm, sw_params);
130     snd_pcm_get_params(pcm, &alsa_buffer_size, &alsa_period_size);
131     /* Don't start until we're [nearly] full */
132     snd_pcm_sw_params_set_start_threshold(pcm, sw_params, (alsa_buffer_size / 2));
133     err = snd_pcm_sw_params(pcm, sw_params);
134 
135     output_buffer_size = snd_pcm_frames_to_bytes(pcm, alsa_buffer_size);
136 
137     if (err < 0)
138     {
139         printf("        Software parameter set failed.\n");
140         goto close_fail;
141     }
142 
143     printf("OK\n");
144 
145     S9xSetSamplesAvailableCallback(alsa_samples_available, this);
146 
147     return true;
148 
149 close_fail:
150     snd_pcm_drain(pcm);
151     snd_pcm_close(pcm);
152     pcm = NULL;
153 
154 fail:
155     printf("Failed: %s\n", snd_strerror(err));
156 
157     return false;
158 }
159 
samples_available()160 void S9xAlsaSoundDriver::samples_available()
161 {
162     snd_pcm_sframes_t frames_written, frames;
163     int bytes;
164 
165     frames = snd_pcm_avail(pcm);
166 
167     if (frames < 0)
168     {
169         frames = snd_pcm_recover(pcm, frames, 1);
170         return;
171     }
172 
173     if (Settings.DynamicRateControl)
174     {
175         S9xUpdateDynamicRate(snd_pcm_frames_to_bytes(pcm, frames),
176                              output_buffer_size);
177     }
178 
179     int snes_frames_available = S9xGetSampleCount() >> 1;
180 
181     if (Settings.DynamicRateControl && !Settings.SoundSync)
182     {
183         // Using rate control, we should always keep the emulator's sound buffers empty to
184         // maintain an accurate measurement.
185         if (frames < snes_frames_available)
186         {
187             S9xClearSamples();
188             return;
189         }
190     }
191 
192     if (Settings.SoundSync && !Settings.TurboMode && !Settings.Mute)
193     {
194         snd_pcm_nonblock(pcm, 0);
195         frames = snes_frames_available;
196     }
197     else
198     {
199         snd_pcm_nonblock(pcm, 1);
200         frames = MIN(frames, snes_frames_available);
201     }
202 
203     bytes = snd_pcm_frames_to_bytes(pcm, frames);
204     if (bytes <= 0)
205         return;
206 
207     if (sound_buffer_size < bytes || sound_buffer == NULL)
208     {
209         sound_buffer = (uint8 *)realloc(sound_buffer, bytes);
210         sound_buffer_size = bytes;
211     }
212 
213     S9xMixSamples(sound_buffer, frames * 2);
214 
215     frames_written = 0;
216 
217     while (frames_written < frames)
218     {
219         int result;
220 
221         result = snd_pcm_writei(pcm,
222                                 sound_buffer +
223                                     snd_pcm_frames_to_bytes(pcm, frames_written),
224                                 frames - frames_written);
225 
226         if (result < 0)
227         {
228             result = snd_pcm_recover(pcm, result, 1);
229 
230             if (result < 0)
231             {
232                 break;
233             }
234         }
235         else
236         {
237             frames_written += result;
238         }
239     }
240 }
241