diff options
author | Solomon Peachy <pizza@shaftnet.org> | 2020-10-22 17:15:45 -0400 |
---|---|---|
committer | Solomon Peachy <pizza@shaftnet.org> | 2020-10-23 17:48:51 -0400 |
commit | 46e357f1bbd459e0c4e3c1d620c8f790cb910c6a (patch) | |
tree | 32a074942abf18c40783cad0e6ff9ff425ad3fc1 /firmware/target/hosted/pcm-alsa.c | |
parent | 2cf75bf008e851831aa8f70ae47be5f980686244 (diff) | |
download | rockbox-46e357f1bbd459e0c4e3c1d620c8f790cb910c6a.tar.gz rockbox-46e357f1bbd459e0c4e3c1d620c8f790cb910c6a.zip |
ALSA: Further rework:
* Get rid of non-async (ie tick task) mode due to inherent brokenness
* Get rid of nonblock mode; we never write if buffers aren't sufficient
* Move driver init into pcm_open() instead of pcm_init()
* Much better underrun handling
* Better error handling in some situations
* Add in recording functionality
* Use smaller audio buffers to avoid glitching
* Don't start audio buffer with silence
* Allow device name to be overridden by target
Recording portions based on work done by Lorenzo Miori in g#633
Change-Id: I0197bdc1749c28109eb79def1e6a3e1d62d8cef3
Diffstat (limited to 'firmware/target/hosted/pcm-alsa.c')
-rw-r--r-- | firmware/target/hosted/pcm-alsa.c | 556 |
1 files changed, 364 insertions, 192 deletions
diff --git a/firmware/target/hosted/pcm-alsa.c b/firmware/target/hosted/pcm-alsa.c index baa841ebe9..14f5160e24 100644 --- a/firmware/target/hosted/pcm-alsa.c +++ b/firmware/target/hosted/pcm-alsa.c | |||
@@ -19,25 +19,16 @@ | |||
19 | * | 19 | * |
20 | ****************************************************************************/ | 20 | ****************************************************************************/ |
21 | 21 | ||
22 | |||
23 | /* | 22 | /* |
24 | * Based, but heavily modified, on the example given at | 23 | * Based, but heavily modified, on the example given at |
25 | * http://www.alsa-project.org/alsa-doc/alsa-lib/_2test_2pcm_8c-example.html | 24 | * http://www.alsa-project.org/alsa-doc/alsa-lib/_2test_2pcm_8c-example.html |
26 | * | 25 | * |
27 | * This driver uses the so-called unsafe async callback method and hardcoded device | 26 | * This driver uses the so-called unsafe async callback method. |
28 | * names. It fails when the audio device is busy by other apps. | ||
29 | * | 27 | * |
30 | * To make the async callback safer, an alternative stack is installed, since | 28 | * To make the async callback safer, an alternative stack is installed, since |
31 | * it's run from a signal hanlder (which otherwise uses the user stack). If | 29 | * it's run from a signal hanlder (which otherwise uses the user stack). |
32 | * tick tasks are run from a signal handler too, please install | ||
33 | * an alternative stack for it too. | ||
34 | * | ||
35 | * TODO: Rewrite this to do it properly with multithreading | ||
36 | * | 30 | * |
37 | * Alternatively, a version using polling in a tick task is provided. While | 31 | * TODO: Rewrite this to properly use multithreading and/or direct mmap() |
38 | * supposedly safer, it appears to use more CPU (however I didn't measure it | ||
39 | * accurately, only looked at htop). At least, in this mode the "default" | ||
40 | * device works which doesnt break with other apps running. | ||
41 | */ | 32 | */ |
42 | 33 | ||
43 | #include "autoconf.h" | 34 | #include "autoconf.h" |
@@ -69,8 +60,7 @@ | |||
69 | * default doesnt seem to work with async callback but doesn't break | 60 | * default doesnt seem to work with async callback but doesn't break |
70 | * with multple applications running */ | 61 | * with multple applications running */ |
71 | #define DEFAULT_PLAYBACK_DEVICE "plughw:0,0" | 62 | #define DEFAULT_PLAYBACK_DEVICE "plughw:0,0" |
72 | 63 | #define DEFAULT_CAPTURE_DEVICE "default" | |
73 | #define USE_ASYNC_CALLBACK | ||
74 | 64 | ||
75 | static const snd_pcm_access_t access_ = SND_PCM_ACCESS_RW_INTERLEAVED; /* access mode */ | 65 | static const snd_pcm_access_t access_ = SND_PCM_ACCESS_RW_INTERLEAVED; /* access mode */ |
76 | #if defined(SONY_NWZ_LINUX) || defined(HAVE_FIIO_LINUX_CODEC) | 66 | #if defined(SONY_NWZ_LINUX) || defined(HAVE_FIIO_LINUX_CODEC) |
@@ -93,12 +83,30 @@ static sample_t *frames = NULL; | |||
93 | static const void *pcm_data = 0; | 83 | static const void *pcm_data = 0; |
94 | static size_t pcm_size = 0; | 84 | static size_t pcm_size = 0; |
95 | 85 | ||
96 | #ifdef USE_ASYNC_CALLBACK | 86 | static snd_async_handler_t *ahandler = NULL; |
97 | static snd_async_handler_t *ahandler; | ||
98 | static pthread_mutex_t pcm_mtx; | 87 | static pthread_mutex_t pcm_mtx; |
99 | static char signal_stack[SIGSTKSZ]; | 88 | static char signal_stack[SIGSTKSZ]; |
100 | #else | 89 | |
101 | static int recursion; | 90 | static const char *playback_dev = DEFAULT_PLAYBACK_DEVICE; |
91 | |||
92 | #ifdef HAVE_RECORDING | ||
93 | static void *pcm_data_rec = DEFAULT_CAPTURE_DEVICE; | ||
94 | static const char *capture_dev = NULL; | ||
95 | static snd_pcm_stream_t current_alsa_mode; /* SND_PCM_STREAM_PLAYBACK / _CAPTURE */ | ||
96 | #endif | ||
97 | |||
98 | static const char *current_alsa_device; | ||
99 | |||
100 | void pcm_alsa_set_playback_device(const char *device) | ||
101 | { | ||
102 | playback_dev = device; | ||
103 | } | ||
104 | |||
105 | #ifdef HAVE_RECORDING | ||
106 | void pcm_alsa_set_capture_device(const char *device) | ||
107 | { | ||
108 | capture_dev = device; | ||
109 | } | ||
102 | #endif | 110 | #endif |
103 | 111 | ||
104 | static int set_hwparams(snd_pcm_t *handle) | 112 | static int set_hwparams(snd_pcm_t *handle) |
@@ -108,44 +116,46 @@ static int set_hwparams(snd_pcm_t *handle) | |||
108 | snd_pcm_hw_params_t *params; | 116 | snd_pcm_hw_params_t *params; |
109 | snd_pcm_hw_params_malloc(¶ms); | 117 | snd_pcm_hw_params_malloc(¶ms); |
110 | 118 | ||
111 | /* Size playback buffers based on sample rate */ | 119 | /* Size playback buffers based on sample rate. |
120 | Note these are in FRAMES, and are sized to be about 10ms | ||
121 | for the buffer and 2.5ms for the period */ | ||
112 | if (pcm_sampr > SAMPR_96) { | 122 | if (pcm_sampr > SAMPR_96) { |
113 | buffer_size = MIX_FRAME_SAMPLES * 32 * 4; /* ~64k */ | 123 | buffer_size = MIX_FRAME_SAMPLES * 16 * 4; /* 32k */ |
114 | period_size = MIX_FRAME_SAMPLES * 4 * 4; /* ~16k */ | 124 | period_size = MIX_FRAME_SAMPLES * 4 * 4; /* 4k */ |
115 | } else if (pcm_sampr > SAMPR_48) { | 125 | } else if (pcm_sampr > SAMPR_48) { |
116 | buffer_size = MIX_FRAME_SAMPLES * 32 * 2; /* ~32k */ | 126 | buffer_size = MIX_FRAME_SAMPLES * 16 * 2; /* 16k */ |
117 | period_size = MIX_FRAME_SAMPLES * 4 * 2; /* ~8k */ | 127 | period_size = MIX_FRAME_SAMPLES * 4 * 2; /* 2k */ |
118 | } else { | 128 | } else { |
119 | buffer_size = MIX_FRAME_SAMPLES * 32; /* ~16k */ | 129 | buffer_size = MIX_FRAME_SAMPLES * 16; /* 4k */ |
120 | period_size = MIX_FRAME_SAMPLES * 4; /* ~4k */ | 130 | period_size = MIX_FRAME_SAMPLES * 4; /* 1k */ |
121 | } | 131 | } |
122 | 132 | ||
123 | /* choose all parameters */ | 133 | /* choose all parameters */ |
124 | err = snd_pcm_hw_params_any(handle, params); | 134 | err = snd_pcm_hw_params_any(handle, params); |
125 | if (err < 0) | 135 | if (err < 0) |
126 | { | 136 | { |
127 | panicf("Broken configuration for playback: no configurations available: %s\n", snd_strerror(err)); | 137 | panicf("Broken configuration for playback: no configurations available: %s", snd_strerror(err)); |
128 | goto error; | 138 | goto error; |
129 | } | 139 | } |
130 | /* set the interleaved read/write format */ | 140 | /* set the interleaved read/write format */ |
131 | err = snd_pcm_hw_params_set_access(handle, params, access_); | 141 | err = snd_pcm_hw_params_set_access(handle, params, access_); |
132 | if (err < 0) | 142 | if (err < 0) |
133 | { | 143 | { |
134 | panicf("Access type not available for playback: %s\n", snd_strerror(err)); | 144 | panicf("Access type not available for playback: %s", snd_strerror(err)); |
135 | goto error; | 145 | goto error; |
136 | } | 146 | } |
137 | /* set the sample format */ | 147 | /* set the sample format */ |
138 | err = snd_pcm_hw_params_set_format(handle, params, format); | 148 | err = snd_pcm_hw_params_set_format(handle, params, format); |
139 | if (err < 0) | 149 | if (err < 0) |
140 | { | 150 | { |
141 | logf("Sample format not available for playback: %s\n", snd_strerror(err)); | 151 | logf("Sample format not available for playback: %s", snd_strerror(err)); |
142 | goto error; | 152 | goto error; |
143 | } | 153 | } |
144 | /* set the count of channels */ | 154 | /* set the count of channels */ |
145 | err = snd_pcm_hw_params_set_channels(handle, params, channels); | 155 | err = snd_pcm_hw_params_set_channels(handle, params, channels); |
146 | if (err < 0) | 156 | if (err < 0) |
147 | { | 157 | { |
148 | logf("Channels count (%i) not available for playbacks: %s\n", channels, snd_strerror(err)); | 158 | logf("Channels count (%i) not available for playbacks: %s", channels, snd_strerror(err)); |
149 | goto error; | 159 | goto error; |
150 | } | 160 | } |
151 | /* set the stream rate */ | 161 | /* set the stream rate */ |
@@ -153,13 +163,13 @@ static int set_hwparams(snd_pcm_t *handle) | |||
153 | err = snd_pcm_hw_params_set_rate_near(handle, params, &srate, 0); | 163 | err = snd_pcm_hw_params_set_rate_near(handle, params, &srate, 0); |
154 | if (err < 0) | 164 | if (err < 0) |
155 | { | 165 | { |
156 | logf("Rate %iHz not available for playback: %s\n", pcm_sampr, snd_strerror(err)); | 166 | logf("Rate %luHz not available for playback: %s", pcm_sampr, snd_strerror(err)); |
157 | goto error; | 167 | goto error; |
158 | } | 168 | } |
159 | real_sample_rate = srate; | 169 | real_sample_rate = srate; |
160 | if (real_sample_rate != pcm_sampr) | 170 | if (real_sample_rate != pcm_sampr) |
161 | { | 171 | { |
162 | logf("Rate doesn't match (requested %iHz, get %iHz)\n", pcm_sampr, real_sample_rate); | 172 | logf("Rate doesn't match (requested %luHz, get %dHz)", pcm_sampr, real_sample_rate); |
163 | err = -EINVAL; | 173 | err = -EINVAL; |
164 | goto error; | 174 | goto error; |
165 | } | 175 | } |
@@ -168,7 +178,7 @@ static int set_hwparams(snd_pcm_t *handle) | |||
168 | err = snd_pcm_hw_params_set_buffer_size_near(handle, params, &buffer_size); | 178 | err = snd_pcm_hw_params_set_buffer_size_near(handle, params, &buffer_size); |
169 | if (err < 0) | 179 | if (err < 0) |
170 | { | 180 | { |
171 | logf("Unable to set buffer size %ld for playback: %s\n", buffer_size, snd_strerror(err)); | 181 | logf("Unable to set buffer size %ld for playback: %s", buffer_size, snd_strerror(err)); |
172 | goto error; | 182 | goto error; |
173 | } | 183 | } |
174 | 184 | ||
@@ -176,7 +186,7 @@ static int set_hwparams(snd_pcm_t *handle) | |||
176 | err = snd_pcm_hw_params_set_period_size_near (handle, params, &period_size, NULL); | 186 | err = snd_pcm_hw_params_set_period_size_near (handle, params, &period_size, NULL); |
177 | if (err < 0) | 187 | if (err < 0) |
178 | { | 188 | { |
179 | logf("Unable to set period size %ld for playback: %s\n", period_size, snd_strerror(err)); | 189 | logf("Unable to set period size %ld for playback: %s", period_size, snd_strerror(err)); |
180 | goto error; | 190 | goto error; |
181 | } | 191 | } |
182 | 192 | ||
@@ -187,7 +197,7 @@ static int set_hwparams(snd_pcm_t *handle) | |||
187 | err = snd_pcm_hw_params(handle, params); | 197 | err = snd_pcm_hw_params(handle, params); |
188 | if (err < 0) | 198 | if (err < 0) |
189 | { | 199 | { |
190 | logf("Unable to set hw params for playback: %s\n", snd_strerror(err)); | 200 | logf("Unable to set hw params for playback: %s", snd_strerror(err)); |
191 | goto error; | 201 | goto error; |
192 | } | 202 | } |
193 | 203 | ||
@@ -209,28 +219,28 @@ static int set_swparams(snd_pcm_t *handle) | |||
209 | err = snd_pcm_sw_params_current(handle, swparams); | 219 | err = snd_pcm_sw_params_current(handle, swparams); |
210 | if (err < 0) | 220 | if (err < 0) |
211 | { | 221 | { |
212 | logf("Unable to determine current swparams for playback: %s\n", snd_strerror(err)); | 222 | logf("Unable to determine current swparams for playback: %s", snd_strerror(err)); |
213 | goto error; | 223 | goto error; |
214 | } | 224 | } |
215 | /* start the transfer when the buffer is half full */ | 225 | /* start the transfer when the buffer is half full */ |
216 | err = snd_pcm_sw_params_set_start_threshold(handle, swparams, buffer_size / 2); | 226 | err = snd_pcm_sw_params_set_start_threshold(handle, swparams, buffer_size / 2); |
217 | if (err < 0) | 227 | if (err < 0) |
218 | { | 228 | { |
219 | logf("Unable to set start threshold mode for playback: %s\n", snd_strerror(err)); | 229 | logf("Unable to set start threshold mode for playback: %s", snd_strerror(err)); |
220 | goto error; | 230 | goto error; |
221 | } | 231 | } |
222 | /* allow the transfer when at least period_size samples can be processed */ | 232 | /* allow the transfer when at least period_size samples can be processed */ |
223 | err = snd_pcm_sw_params_set_avail_min(handle, swparams, period_size); | 233 | err = snd_pcm_sw_params_set_avail_min(handle, swparams, period_size); |
224 | if (err < 0) | 234 | if (err < 0) |
225 | { | 235 | { |
226 | logf("Unable to set avail min for playback: %s\n", snd_strerror(err)); | 236 | logf("Unable to set avail min for playback: %s", snd_strerror(err)); |
227 | goto error; | 237 | goto error; |
228 | } | 238 | } |
229 | /* write the parameters to the playback device */ | 239 | /* write the parameters to the playback device */ |
230 | err = snd_pcm_sw_params(handle, swparams); | 240 | err = snd_pcm_sw_params(handle, swparams); |
231 | if (err < 0) | 241 | if (err < 0) |
232 | { | 242 | { |
233 | logf("Unable to set sw params for playback: %s\n", snd_strerror(err)); | 243 | logf("Unable to set sw params for playback: %s", snd_strerror(err)); |
234 | goto error; | 244 | goto error; |
235 | } | 245 | } |
236 | 246 | ||
@@ -273,20 +283,20 @@ void pcm_alsa_set_digital_volume(int vol_db_l, int vol_db_r) | |||
273 | dig_vol_mult_l = 1 << vol_shift_l | 1 << (vol_shift_l - 2); | 283 | dig_vol_mult_l = 1 << vol_shift_l | 1 << (vol_shift_l - 2); |
274 | else | 284 | else |
275 | dig_vol_mult_l = 1 << vol_shift_l | 1 << (vol_shift_l - 1); | 285 | dig_vol_mult_l = 1 << vol_shift_l | 1 << (vol_shift_l - 1); |
276 | logf("l: %d dB -> factor = %d\n", vol_db_l - 48, dig_vol_mult_l); | 286 | logf("l: %d dB -> factor = %d", vol_db_l - 48, dig_vol_mult_l); |
277 | if(r_r == 0) | 287 | if(r_r == 0) |
278 | dig_vol_mult_r = 1 << vol_shift_r; | 288 | dig_vol_mult_r = 1 << vol_shift_r; |
279 | else if(r_r == 1) | 289 | else if(r_r == 1) |
280 | dig_vol_mult_r = 1 << vol_shift_r | 1 << (vol_shift_r - 2); | 290 | dig_vol_mult_r = 1 << vol_shift_r | 1 << (vol_shift_r - 2); |
281 | else | 291 | else |
282 | dig_vol_mult_r = 1 << vol_shift_r | 1 << (vol_shift_r - 1); | 292 | dig_vol_mult_r = 1 << vol_shift_r | 1 << (vol_shift_r - 1); |
283 | logf("r: %d dB -> factor = %d\n", vol_db_r - 48, dig_vol_mult_r); | 293 | logf("r: %d dB -> factor = %d", vol_db_r - 48, dig_vol_mult_r); |
284 | } | 294 | } |
285 | 295 | ||
286 | /* copy pcm samples to a spare buffer, suitable for snd_pcm_writei() */ | 296 | /* copy pcm samples to a spare buffer, suitable for snd_pcm_writei() */ |
287 | static bool fill_frames(void) | 297 | static bool copy_frames(bool first) |
288 | { | 298 | { |
289 | ssize_t copy_n, frames_left = period_size; | 299 | ssize_t nframes, frames_left = period_size; |
290 | bool new_buffer = false; | 300 | bool new_buffer = false; |
291 | 301 | ||
292 | while (frames_left > 0) | 302 | while (frames_left > 0) |
@@ -294,24 +304,41 @@ static bool fill_frames(void) | |||
294 | if (!pcm_size) | 304 | if (!pcm_size) |
295 | { | 305 | { |
296 | new_buffer = true; | 306 | new_buffer = true; |
297 | if (!pcm_play_dma_complete_callback(PCM_DMAST_OK, &pcm_data, | 307 | #ifdef HAVE_RECORDING |
298 | &pcm_size)) | 308 | switch (current_alsa_mode) |
299 | { | 309 | { |
300 | return false; | 310 | case SND_PCM_STREAM_PLAYBACK: |
311 | #endif | ||
312 | if (!pcm_play_dma_complete_callback(PCM_DMAST_OK, &pcm_data, &pcm_size)) | ||
313 | { | ||
314 | return false; | ||
315 | } | ||
316 | #ifdef HAVE_RECORDING | ||
317 | break; | ||
318 | case SND_PCM_STREAM_CAPTURE: | ||
319 | if (!pcm_play_dma_complete_callback(PCM_DMAST_OK, &pcm_data, &pcm_size)) | ||
320 | { | ||
321 | return false; | ||
322 | } | ||
323 | break; | ||
324 | default: | ||
325 | break; | ||
301 | } | 326 | } |
327 | #endif | ||
302 | } | 328 | } |
303 | 329 | ||
330 | /* Note: This assumes stereo 16-bit */ | ||
304 | if (pcm_size % 4) | 331 | if (pcm_size % 4) |
305 | panicf("Wrong pcm_size"); | 332 | panicf("Wrong pcm_size"); |
306 | /* the compiler will optimize this test away */ | 333 | /* the compiler will optimize this test away */ |
307 | copy_n = MIN((ssize_t)pcm_size/4, frames_left); | 334 | nframes = MIN((ssize_t)pcm_size/4, frames_left); |
308 | if (format == SND_PCM_FORMAT_S32_LE) | 335 | if (format == SND_PCM_FORMAT_S32_LE) |
309 | { | 336 | { |
310 | /* We have to convert 16-bit to 32-bit, the need to multiply the | 337 | /* We have to convert 16-bit to 32-bit, the need to multiply the |
311 | * sample by some value so the sound is not too low */ | 338 | * sample by some value so the sound is not too low */ |
312 | const short *pcm_ptr = pcm_data; | 339 | const short *pcm_ptr = pcm_data; |
313 | sample_t *sample_ptr = &frames[2*(period_size-frames_left)]; | 340 | sample_t *sample_ptr = &frames[2*(period_size-frames_left)]; |
314 | for (int i = 0; i < copy_n; i++) | 341 | for (int i = 0; i < nframes; i++) |
315 | { | 342 | { |
316 | *sample_ptr++ = *pcm_ptr++ * dig_vol_mult_l; | 343 | *sample_ptr++ = *pcm_ptr++ * dig_vol_mult_l; |
317 | *sample_ptr++ = *pcm_ptr++ * dig_vol_mult_r; | 344 | *sample_ptr++ = *pcm_ptr++ * dig_vol_mult_r; |
@@ -319,66 +346,203 @@ static bool fill_frames(void) | |||
319 | } | 346 | } |
320 | else | 347 | else |
321 | { | 348 | { |
322 | /* Rockbox and PCM have same format: memcopy */ | 349 | #ifdef HAVE_RECORDING |
323 | memcpy(&frames[2*(period_size-frames_left)], pcm_data, copy_n * 4); | 350 | switch (current_alsa_mode) |
351 | { | ||
352 | case SND_PCM_STREAM_PLAYBACK: | ||
353 | #endif | ||
354 | /* Rockbox and PCM have same format: memcopy */ | ||
355 | memcpy(&frames[2*(period_size-frames_left)], pcm_data, nframes * 4); | ||
356 | #ifdef HAVE_RECORDING | ||
357 | break; | ||
358 | case SND_PCM_STREAM_CAPTURE: | ||
359 | memcpy(pcm_data_rec, &frames[2*(period_size-frames_left)], nframes * 4); | ||
360 | break; | ||
361 | default: | ||
362 | break; | ||
363 | } | ||
364 | #endif | ||
324 | } | 365 | } |
325 | pcm_data += copy_n*4; | 366 | pcm_data += nframes*4; |
326 | pcm_size -= copy_n*4; | 367 | pcm_size -= nframes*4; |
327 | frames_left -= copy_n; | 368 | frames_left -= nframes; |
328 | 369 | ||
329 | if (new_buffer) | 370 | if (new_buffer && !first) |
330 | { | 371 | { |
331 | new_buffer = false; | 372 | new_buffer = false; |
332 | pcm_play_dma_status_callback(PCM_DMAST_STARTED); | 373 | #ifdef HAVE_RECORDING |
374 | switch (current_alsa_mode) | ||
375 | { | ||
376 | case SND_PCM_STREAM_PLAYBACK: | ||
377 | #endif | ||
378 | pcm_play_dma_status_callback(PCM_DMAST_STARTED); | ||
379 | #ifdef HAVE_RECORDING | ||
380 | break; | ||
381 | case SND_PCM_STREAM_CAPTURE: | ||
382 | pcm_rec_dma_status_callback(PCM_DMAST_STARTED); | ||
383 | break; | ||
384 | default: | ||
385 | break; | ||
386 | } | ||
387 | #endif | ||
333 | } | 388 | } |
334 | } | 389 | } |
335 | 390 | ||
336 | return true; | 391 | return true; |
337 | } | 392 | } |
338 | 393 | ||
339 | #ifdef USE_ASYNC_CALLBACK | ||
340 | static void async_callback(snd_async_handler_t *ahandler) | 394 | static void async_callback(snd_async_handler_t *ahandler) |
341 | { | 395 | { |
396 | int err; | ||
397 | |||
398 | if (!ahandler) return; | ||
399 | |||
342 | snd_pcm_t *handle = snd_async_handler_get_pcm(ahandler); | 400 | snd_pcm_t *handle = snd_async_handler_get_pcm(ahandler); |
343 | 401 | ||
402 | if (!handle) return; | ||
403 | |||
344 | if (pthread_mutex_trylock(&pcm_mtx) != 0) | 404 | if (pthread_mutex_trylock(&pcm_mtx) != 0) |
345 | return; | 405 | return; |
346 | #else | ||
347 | static void pcm_tick(void) | ||
348 | { | ||
349 | if (snd_pcm_state(handle) != SND_PCM_STATE_RUNNING) | ||
350 | return; | ||
351 | #endif | ||
352 | 406 | ||
353 | while (snd_pcm_avail_update(handle) >= period_size) | 407 | snd_pcm_state_t state = snd_pcm_state(handle); |
408 | |||
409 | if (state == SND_PCM_STATE_XRUN) | ||
410 | { | ||
411 | logf("underrun!"); | ||
412 | err = snd_pcm_recover(handle, -EPIPE, 0); | ||
413 | if (err < 0) { | ||
414 | logf("XRUN Recovery error: %s", snd_strerror(err)); | ||
415 | goto abort; | ||
416 | } | ||
417 | } | ||
418 | else if (state == SND_PCM_STATE_DRAINING) | ||
354 | { | 419 | { |
355 | if (fill_frames()) | 420 | logf("draining..."); |
421 | goto abort; | ||
422 | } | ||
423 | else if (state == SND_PCM_STATE_SETUP) | ||
424 | { | ||
425 | goto abort; | ||
426 | } | ||
427 | |||
428 | #ifdef HAVE_RECORDING | ||
429 | if (current_alsa_mode == SND_PCM_STREAM_PLAYBACK) | ||
430 | { | ||
431 | #endif | ||
432 | while (snd_pcm_avail_update(handle) >= period_size) | ||
356 | { | 433 | { |
357 | int err = snd_pcm_writei(handle, frames, period_size); | 434 | if (copy_frames(false)) |
358 | if (err < 0 && err != period_size && err != -EAGAIN) | 435 | { |
436 | err = snd_pcm_writei(handle, frames, period_size); | ||
437 | if (err < 0 && err != period_size && err != -EAGAIN) | ||
438 | { | ||
439 | logf("Write error: written %i expected %li", err, period_size); | ||
440 | break; | ||
441 | } | ||
442 | } | ||
443 | else | ||
359 | { | 444 | { |
360 | logf("Write error: written %i expected %li\n", err, period_size); | 445 | logf("%s: No Data (%d).", __func__, state); |
361 | break; | 446 | break; |
362 | } | 447 | } |
363 | } | 448 | } |
364 | else | 449 | #ifdef HAVE_RECORDING |
450 | } | ||
451 | else if (current_alsa_mode == SND_PCM_STREAM_CAPTURE) | ||
452 | { | ||
453 | while (snd_pcm_avail_update(handle) >= period_size) | ||
365 | { | 454 | { |
366 | logf("%s: No Data.\n", __func__); | 455 | int err = snd_pcm_readi(handle, frames, period_size); |
367 | break; | 456 | if (err < 0 && err != period_size && err != -EAGAIN) |
457 | { | ||
458 | logf("Read error: read %i expected %li", err, period_size); | ||
459 | break; | ||
460 | } | ||
461 | |||
462 | /* start the fake DMA transfer */ | ||
463 | if (!copy_frames(false)) | ||
464 | { | ||
465 | /* do not spam logf */ | ||
466 | /* logf("%s: No Data.", __func__); */ | ||
467 | break; | ||
468 | } | ||
368 | } | 469 | } |
369 | } | 470 | } |
370 | #ifdef USE_ASYNC_CALLBACK | 471 | #endif |
472 | |||
473 | if (snd_pcm_state(handle) == SND_PCM_STATE_PREPARED) | ||
474 | { | ||
475 | err = snd_pcm_start(handle); | ||
476 | if (err < 0) { | ||
477 | logf("cb start error: %s", snd_strerror(err)); | ||
478 | goto abort; | ||
479 | } | ||
480 | } | ||
481 | |||
482 | abort: | ||
371 | pthread_mutex_unlock(&pcm_mtx); | 483 | pthread_mutex_unlock(&pcm_mtx); |
484 | } | ||
485 | |||
486 | static void close_hwdev(void) | ||
487 | { | ||
488 | logf("closedev (%p)", handle); | ||
489 | |||
490 | if (handle) { | ||
491 | snd_pcm_drain(handle); | ||
492 | #ifdef AUDIOHW_MUTE_ON_PAUSE | ||
493 | audiohw_mute(true); | ||
372 | #endif | 494 | #endif |
495 | if (ahandler) { | ||
496 | snd_async_del_handler(ahandler); | ||
497 | ahandler = NULL; | ||
498 | } | ||
499 | snd_pcm_close(handle); | ||
500 | |||
501 | handle = NULL; | ||
502 | } | ||
503 | current_alsa_device = NULL; | ||
504 | |||
505 | #ifdef HAVE_RECORDING | ||
506 | pcm_data_rec = NULL; | ||
507 | #endif | ||
508 | } | ||
509 | |||
510 | static void alsadev_cleanup(void) | ||
511 | { | ||
512 | free(frames); | ||
513 | frames = NULL; | ||
514 | close_hwdev(); | ||
373 | } | 515 | } |
374 | 516 | ||
375 | static int async_rw(snd_pcm_t *handle) | 517 | static void open_hwdev(const char *device, snd_pcm_stream_t mode) |
376 | { | 518 | { |
377 | int err; | 519 | int err; |
378 | snd_pcm_sframes_t sample_size; | ||
379 | sample_t *samples; | ||
380 | 520 | ||
381 | #ifdef USE_ASYNC_CALLBACK | 521 | logf("opendev %s (%p)", device, handle); |
522 | |||
523 | if (handle && device == current_alsa_device | ||
524 | #ifdef HAVE_RECORDING | ||
525 | && current_alsa_mode == mode | ||
526 | #endif | ||
527 | ) | ||
528 | { | ||
529 | return; | ||
530 | } | ||
531 | |||
532 | /* Close old handle */ | ||
533 | close_hwdev(); | ||
534 | |||
535 | if ((err = snd_pcm_open(&handle, device, SND_PCM_STREAM_PLAYBACK, 0)) < 0) | ||
536 | { | ||
537 | panicf("%s(): Cannot open device %s: %s", __func__, device, snd_strerror(err)); | ||
538 | } | ||
539 | last_sample_rate = 0; | ||
540 | |||
541 | pthread_mutexattr_t attr; | ||
542 | pthread_mutexattr_init(&attr); | ||
543 | pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); | ||
544 | pthread_mutex_init(&pcm_mtx, &attr); | ||
545 | |||
382 | /* assign alternative stack for the signal handlers */ | 546 | /* assign alternative stack for the signal handlers */ |
383 | stack_t ss = { | 547 | stack_t ss = { |
384 | .ss_sp = signal_stack, | 548 | .ss_sp = signal_stack, |
@@ -390,15 +554,13 @@ static int async_rw(snd_pcm_t *handle) | |||
390 | err = sigaltstack(&ss, NULL); | 554 | err = sigaltstack(&ss, NULL); |
391 | if (err < 0) | 555 | if (err < 0) |
392 | { | 556 | { |
393 | logf("Unable to install alternative signal stack: %s", strerror(err)); | 557 | panicf("Unable to install alternative signal stack: %s", strerror(err)); |
394 | return err; | ||
395 | } | 558 | } |
396 | 559 | ||
397 | err = snd_async_add_pcm_handler(&ahandler, handle, async_callback, NULL); | 560 | err = snd_async_add_pcm_handler(&ahandler, handle, async_callback, NULL); |
398 | if (err < 0) | 561 | if (err < 0) |
399 | { | 562 | { |
400 | logf("Unable to register async handler: %s\n", snd_strerror(err)); | 563 | panicf("Unable to register async handler: %s", snd_strerror(err)); |
401 | return err; | ||
402 | } | 564 | } |
403 | 565 | ||
404 | /* only modify the stack the handler runs on */ | 566 | /* only modify the stack the handler runs on */ |
@@ -407,76 +569,17 @@ static int async_rw(snd_pcm_t *handle) | |||
407 | err = sigaction(SIGIO, &sa, NULL); | 569 | err = sigaction(SIGIO, &sa, NULL); |
408 | if (err < 0) | 570 | if (err < 0) |
409 | { | 571 | { |
410 | logf("Unable to install alternative signal stack: %s", strerror(err)); | 572 | panicf("Unable to install alternative signal stack: %s", strerror(err)); |
411 | return err; | ||
412 | } | ||
413 | #endif | ||
414 | |||
415 | /* fill buffer with silence to initiate playback without noisy click */ | ||
416 | sample_size = buffer_size; | ||
417 | samples = calloc(1, sample_size * channels * sizeof(sample_t)); | ||
418 | |||
419 | snd_pcm_format_set_silence(format, samples, sample_size); | ||
420 | err = snd_pcm_writei(handle, samples, sample_size); | ||
421 | free(samples); | ||
422 | |||
423 | if (err < 0) | ||
424 | { | ||
425 | logf("Initial write error: %s\n", snd_strerror(err)); | ||
426 | return err; | ||
427 | } | ||
428 | if (err != (ssize_t)sample_size) | ||
429 | { | ||
430 | logf("Initial write error: written %i expected %li\n", err, sample_size); | ||
431 | return err; | ||
432 | } | 573 | } |
433 | 574 | ||
434 | snd_pcm_state_t state = snd_pcm_state(handle); | 575 | #ifdef HAVE_RECORDING |
435 | logf("PCM RW State %d", state); | 576 | current_alsa_mode = mode; |
436 | if (state == SND_PCM_STATE_PREPARED) | 577 | #else |
437 | { | 578 | (void)mode; |
438 | err = snd_pcm_start(handle); | 579 | #endif |
439 | if (err < 0) | 580 | current_alsa_device = device; |
440 | { | ||
441 | logf("Start error: %s\n", snd_strerror(err)); | ||
442 | return err; | ||
443 | } | ||
444 | } else { | ||
445 | return state; | ||
446 | } | ||
447 | |||
448 | return 0; | ||
449 | } | ||
450 | |||
451 | void cleanup(void) | ||
452 | { | ||
453 | free(frames); | ||
454 | frames = NULL; | ||
455 | snd_pcm_close(handle); | ||
456 | handle = NULL; | ||
457 | } | ||
458 | |||
459 | static void open_hwdev(const char *device) | ||
460 | { | ||
461 | int err; | ||
462 | |||
463 | logf("opendev %s (%p)", device, handle); | ||
464 | |||
465 | /* Close old handle first, if needed */ | ||
466 | if (handle) { | ||
467 | pcm_play_dma_stop(); | ||
468 | snd_pcm_close(handle); | ||
469 | handle = NULL; | ||
470 | } | ||
471 | |||
472 | if ((err = snd_pcm_open(&handle, device, SND_PCM_STREAM_PLAYBACK, 0)) < 0) | ||
473 | { | ||
474 | panicf("%s(): Cannot open device %s: %s\n", __func__, device, snd_strerror(err)); | ||
475 | } | ||
476 | if ((err = snd_pcm_nonblock(handle, 1))) | ||
477 | panicf("Could not set non-block mode: %s\n", snd_strerror(err)); | ||
478 | 581 | ||
479 | last_sample_rate = 0; | 582 | atexit(alsadev_cleanup); |
480 | } | 583 | } |
481 | 584 | ||
482 | void pcm_play_dma_init(void) | 585 | void pcm_play_dma_init(void) |
@@ -485,51 +588,30 @@ void pcm_play_dma_init(void) | |||
485 | 588 | ||
486 | audiohw_preinit(); | 589 | audiohw_preinit(); |
487 | 590 | ||
488 | open_hwdev(DEFAULT_PLAYBACK_DEVICE); | 591 | open_hwdev(playback_dev, SND_PCM_STREAM_PLAYBACK); |
489 | |||
490 | #ifdef USE_ASYNC_CALLBACK | ||
491 | pthread_mutexattr_t attr; | ||
492 | pthread_mutexattr_init(&attr); | ||
493 | pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); | ||
494 | pthread_mutex_init(&pcm_mtx, &attr); | ||
495 | #else | ||
496 | tick_add_task(pcm_tick); | ||
497 | #endif | ||
498 | 592 | ||
499 | atexit(cleanup); | ||
500 | return; | 593 | return; |
501 | } | 594 | } |
502 | 595 | ||
503 | void pcm_play_lock(void) | 596 | void pcm_play_lock(void) |
504 | { | 597 | { |
505 | #ifdef USE_ASYNC_CALLBACK | ||
506 | pthread_mutex_lock(&pcm_mtx); | 598 | pthread_mutex_lock(&pcm_mtx); |
507 | #else | ||
508 | if (recursion++ == 0) | ||
509 | tick_remove_task(pcm_tick); | ||
510 | #endif | ||
511 | } | 599 | } |
512 | 600 | ||
513 | void pcm_play_unlock(void) | 601 | void pcm_play_unlock(void) |
514 | { | 602 | { |
515 | #ifdef USE_ASYNC_CALLBACK | ||
516 | pthread_mutex_unlock(&pcm_mtx); | 603 | pthread_mutex_unlock(&pcm_mtx); |
517 | #else | ||
518 | if (--recursion == 0) | ||
519 | tick_add_task(pcm_tick); | ||
520 | #endif | ||
521 | } | 604 | } |
522 | 605 | ||
523 | static void pcm_dma_apply_settings_nolock(void) | 606 | static void pcm_dma_apply_settings_nolock(void) |
524 | { | 607 | { |
525 | logf("PCM DMA Settings %d %d", last_sample_rate, pcm_sampr); | 608 | logf("PCM DMA Settings %d %lu", last_sample_rate, pcm_sampr); |
526 | 609 | ||
527 | if (last_sample_rate != pcm_sampr) | 610 | if (last_sample_rate != pcm_sampr) |
528 | { | 611 | { |
529 | last_sample_rate = pcm_sampr; | 612 | last_sample_rate = pcm_sampr; |
530 | 613 | ||
531 | #ifdef AUDIOHW_MUTE_ON_SRATE_CHANGE | 614 | #ifdef AUDIOHW_MUTE_ON_SRATE_CHANGE |
532 | // XXX AK4450 (xDuoo X3ii) needs to be muted when switching rates. | ||
533 | audiohw_mute(true); | 615 | audiohw_mute(true); |
534 | #endif | 616 | #endif |
535 | snd_pcm_drop(handle); | 617 | snd_pcm_drop(handle); |
@@ -555,6 +637,8 @@ void pcm_dma_apply_settings(void) | |||
555 | void pcm_play_dma_pause(bool pause) | 637 | void pcm_play_dma_pause(bool pause) |
556 | { | 638 | { |
557 | logf("PCM DMA pause %d", pause); | 639 | logf("PCM DMA pause %d", pause); |
640 | if (!handle) return; | ||
641 | |||
558 | #ifdef AUDIOHW_MUTE_ON_PAUSE | 642 | #ifdef AUDIOHW_MUTE_ON_PAUSE |
559 | if (pause) audiohw_mute(true); | 643 | if (pause) audiohw_mute(true); |
560 | #endif | 644 | #endif |
@@ -566,14 +650,15 @@ void pcm_play_dma_pause(bool pause) | |||
566 | 650 | ||
567 | void pcm_play_dma_stop(void) | 651 | void pcm_play_dma_stop(void) |
568 | { | 652 | { |
569 | snd_pcm_nonblock(handle, 0); | 653 | logf("PCM DMA stop (%d)", snd_pcm_state(handle)); |
570 | snd_pcm_drain(handle); | 654 | |
571 | snd_pcm_nonblock(handle, 1); | 655 | int err = snd_pcm_drain(handle); |
572 | // last_sample_rate = 0; | 656 | if (err < 0) |
657 | if (err < 0) | ||
658 | logf("Drain failed: %s", snd_strerror(err)); | ||
573 | #ifdef AUDIOHW_MUTE_ON_PAUSE | 659 | #ifdef AUDIOHW_MUTE_ON_PAUSE |
574 | audiohw_mute(true); | 660 | audiohw_mute(true); |
575 | #endif | 661 | #endif |
576 | logf("PCM DMA stopped"); | ||
577 | } | 662 | } |
578 | 663 | ||
579 | void pcm_play_dma_start(const void *addr, size_t size) | 664 | void pcm_play_dma_start(const void *addr, size_t size) |
@@ -591,40 +676,67 @@ void pcm_play_dma_start(const void *addr, size_t size) | |||
591 | while (1) | 676 | while (1) |
592 | { | 677 | { |
593 | snd_pcm_state_t state = snd_pcm_state(handle); | 678 | snd_pcm_state_t state = snd_pcm_state(handle); |
594 | logf("PCM State %d", state); | 679 | logf("PCM State %d", state); |
595 | 680 | ||
596 | switch (state) | 681 | switch (state) |
597 | { | 682 | { |
598 | case SND_PCM_STATE_RUNNING: | 683 | case SND_PCM_STATE_RUNNING: |
684 | #if defined(AUDIOHW_MUTE_ON_PAUSE) | ||
685 | audiohw_mute(false); | ||
686 | #endif | ||
599 | return; | 687 | return; |
600 | case SND_PCM_STATE_XRUN: | 688 | case SND_PCM_STATE_XRUN: |
601 | { | 689 | { |
602 | logf("Trying to recover from error\n"); | 690 | logf("Trying to recover from error"); |
603 | int err = snd_pcm_recover(handle, -EPIPE, 0); | 691 | int err = snd_pcm_recover(handle, -EPIPE, 0); |
604 | if (err < 0) | 692 | if (err < 0) |
605 | logf("Recovery failed: %s\n", snd_strerror(err)); | 693 | logf("Recovery failed: %s", snd_strerror(err)); |
606 | continue; | 694 | continue; |
607 | } | 695 | } |
608 | case SND_PCM_STATE_SETUP: | 696 | case SND_PCM_STATE_SETUP: |
609 | { | 697 | { |
610 | int err = snd_pcm_prepare(handle); | 698 | int err = snd_pcm_prepare(handle); |
611 | if (err < 0) | 699 | if (err < 0) |
612 | logf("Prepare error: %s\n", snd_strerror(err)); | 700 | logf("Prepare error: %s", snd_strerror(err)); |
613 | /* fall through */ | ||
614 | } | 701 | } |
702 | /* fall through */ | ||
615 | case SND_PCM_STATE_PREPARED: | 703 | case SND_PCM_STATE_PREPARED: |
616 | { /* prepared state, we need to fill the buffer with silence before | 704 | { |
617 | * starting */ | 705 | int err; |
618 | int err = async_rw(handle); | 706 | #if 0 |
619 | if (err < 0) { | 707 | /* fill buffer with silence to initiate playback without noisy click */ |
620 | logf("Start error: %s\n", snd_strerror(err)); | 708 | snd_pcm_sframes_t sample_size = buffer_size; |
709 | sample_t *samples = calloc(1, sample_size * channels * sizeof(sample_t)); | ||
710 | |||
711 | snd_pcm_format_set_silence(format, samples, sample_size); | ||
712 | err = snd_pcm_writei(handle, samples, sample_size); | ||
713 | free(samples); | ||
714 | |||
715 | if (err != (ssize_t)sample_size) | ||
716 | { | ||
717 | logf("Initial write error: written %i expected %li", err, sample_size); | ||
621 | return; | 718 | return; |
622 | } | 719 | } |
623 | #if defined(AUDIOHW_MUTE_ON_PAUSE) | 720 | #else |
624 | audiohw_mute(false); | 721 | /* Fill buffer with proper sample data */ |
722 | while (snd_pcm_avail_update(handle) >= period_size) | ||
723 | { | ||
724 | if (copy_frames(true)) | ||
725 | { | ||
726 | err = snd_pcm_writei(handle, frames, period_size); | ||
727 | if (err < 0 && err != period_size && err != -EAGAIN) | ||
728 | { | ||
729 | logf("Write error: written %i expected %li", err, period_size); | ||
730 | break; | ||
731 | } | ||
732 | } | ||
733 | } | ||
625 | #endif | 734 | #endif |
626 | if (err == 0) | 735 | err = snd_pcm_start(handle); |
627 | return; | 736 | if (err < 0) { |
737 | logf("start error: %s", snd_strerror(err)); | ||
738 | } | ||
739 | |||
628 | break; | 740 | break; |
629 | } | 741 | } |
630 | case SND_PCM_STATE_PAUSED: | 742 | case SND_PCM_STATE_PAUSED: |
@@ -636,7 +748,7 @@ void pcm_play_dma_start(const void *addr, size_t size) | |||
636 | /* run until drained */ | 748 | /* run until drained */ |
637 | continue; | 749 | continue; |
638 | default: | 750 | default: |
639 | logf("Unhandled state: %s\n", snd_pcm_state_name(state)); | 751 | logf("Unhandled state: %s", snd_pcm_state_name(state)); |
640 | return; | 752 | return; |
641 | } | 753 | } |
642 | } | 754 | } |
@@ -676,40 +788,100 @@ int pcm_alsa_get_rate(void) | |||
676 | #ifdef HAVE_RECORDING | 788 | #ifdef HAVE_RECORDING |
677 | void pcm_rec_lock(void) | 789 | void pcm_rec_lock(void) |
678 | { | 790 | { |
791 | pcm_play_lock(); | ||
679 | } | 792 | } |
680 | 793 | ||
681 | void pcm_rec_unlock(void) | 794 | void pcm_rec_unlock(void) |
682 | { | 795 | { |
796 | pcm_play_unlock(); | ||
683 | } | 797 | } |
684 | 798 | ||
685 | void pcm_rec_dma_init(void) | 799 | void pcm_rec_dma_init(void) |
686 | { | 800 | { |
801 | logf("PCM REC DMA Init"); | ||
802 | |||
803 | open_hwdev(capture_dev, SND_PCM_STREAM_CAPTURE); | ||
687 | } | 804 | } |
688 | 805 | ||
689 | void pcm_rec_dma_close(void) | 806 | void pcm_rec_dma_close(void) |
690 | { | 807 | { |
808 | logf("Rec DMA Close"); | ||
809 | close_hwdev(); | ||
691 | } | 810 | } |
692 | 811 | ||
693 | void pcm_rec_dma_start(void *start, size_t size) | 812 | void pcm_rec_dma_start(void *start, size_t size) |
694 | { | 813 | { |
695 | (void)start; | 814 | logf("PCM REC DMA start (%p %d)", start, size); |
696 | (void)size; | 815 | pcm_dma_apply_settings_nolock(); |
816 | pcm_data_rec = start; | ||
817 | pcm_size = size; | ||
818 | |||
819 | if (!handle) return; | ||
820 | |||
821 | while (1) | ||
822 | { | ||
823 | snd_pcm_state_t state = snd_pcm_state(handle); | ||
824 | |||
825 | switch (state) | ||
826 | { | ||
827 | case SND_PCM_STATE_RUNNING: | ||
828 | return; | ||
829 | case SND_PCM_STATE_XRUN: | ||
830 | { | ||
831 | logf("Trying to recover from error"); | ||
832 | int err = snd_pcm_recover(handle, -EPIPE, 0); | ||
833 | if (err < 0) | ||
834 | panicf("Recovery failed: %s", snd_strerror(err)); | ||
835 | continue; | ||
836 | } | ||
837 | case SND_PCM_STATE_SETUP: | ||
838 | { | ||
839 | int err = snd_pcm_prepare(handle); | ||
840 | if (err < 0) | ||
841 | panicf("Prepare error: %s", snd_strerror(err)); | ||
842 | } | ||
843 | /* fall through */ | ||
844 | case SND_PCM_STATE_PREPARED: | ||
845 | { | ||
846 | int err = snd_pcm_start(handle); | ||
847 | if (err < 0) | ||
848 | panicf("Start error: %s", snd_strerror(err)); | ||
849 | return; | ||
850 | } | ||
851 | case SND_PCM_STATE_PAUSED: | ||
852 | { /* paused, simply resume */ | ||
853 | pcm_play_dma_pause(0); | ||
854 | return; | ||
855 | } | ||
856 | case SND_PCM_STATE_DRAINING: | ||
857 | /* run until drained */ | ||
858 | continue; | ||
859 | default: | ||
860 | logf("Unhandled state: %s", snd_pcm_state_name(state)); | ||
861 | return; | ||
862 | } | ||
863 | } | ||
697 | } | 864 | } |
698 | 865 | ||
699 | void pcm_rec_dma_stop(void) | 866 | void pcm_rec_dma_stop(void) |
700 | { | 867 | { |
868 | logf("Rec DMA Stop"); | ||
869 | close_hwdev(); | ||
701 | } | 870 | } |
702 | 871 | ||
703 | const void * pcm_rec_dma_get_peak_buffer(void) | 872 | const void * pcm_rec_dma_get_peak_buffer(void) |
704 | { | 873 | { |
705 | return NULL; | 874 | uintptr_t addr = (uintptr_t)pcm_data_rec; |
875 | return (void*)((addr + 3) & ~3); | ||
706 | } | 876 | } |
707 | 877 | ||
878 | #ifdef SIMULATOR | ||
708 | void audiohw_set_recvol(int left, int right, int type) | 879 | void audiohw_set_recvol(int left, int right, int type) |
709 | { | 880 | { |
710 | (void)left; | 881 | (void)left; |
711 | (void)right; | 882 | (void)right; |
712 | (void)type; | 883 | (void)type; |
713 | } | 884 | } |
885 | #endif | ||
714 | 886 | ||
715 | #endif /* HAVE_RECORDING */ | 887 | #endif /* HAVE_RECORDING */ |