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