00001 #include "audiodevice.h"
00002 #include "audioalsa.h"
00003 #include "bcsignals.h"
00004 #include "playbackconfig.h"
00005 #include "preferences.h"
00006 #include "recordconfig.h"
00007
00008 #include <errno.h>
00009
00010 #ifdef HAVE_ALSA
00011
00012 AudioALSA::AudioALSA(AudioDevice *device)
00013 : AudioLowLevel(device)
00014 {
00015 samples_written = 0;
00016 timer = new Timer;
00017 delay = 0;
00018 timer_lock = new Mutex("AudioALSA::timer_lock");
00019 interrupted = 0;
00020 }
00021
00022 AudioALSA::~AudioALSA()
00023 {
00024 delete timer_lock;
00025 delete timer;
00026 }
00027
00028 void AudioALSA::list_devices(ArrayList<char*> *devices, int pcm_title)
00029 {
00030 snd_ctl_t *handle;
00031 int card, err, dev, idx;
00032 snd_ctl_card_info_t *info;
00033 snd_pcm_info_t *pcminfo;
00034 char string[BCTEXTLEN];
00035 snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK;
00036 int error;
00037
00038 snd_ctl_card_info_alloca(&info);
00039 snd_pcm_info_alloca(&pcminfo);
00040
00041 card = -1;
00042 #define DEFAULT_DEVICE "default"
00043 char *result = new char[strlen(DEFAULT_DEVICE) + 1];
00044 devices->append(result);
00045 strcpy(result, DEFAULT_DEVICE);
00046
00047 while(snd_card_next(&card) >= 0)
00048 {
00049 char name[BCTEXTLEN];
00050 if(card < 0) break;
00051 sprintf(name, "hw:%i", card);
00052
00053 if((err = snd_ctl_open(&handle, name, 0)) < 0)
00054 {
00055 printf("AudioALSA::list_devices (%i): %s\n", card, snd_strerror(err));
00056 continue;
00057 }
00058
00059 if((err = snd_ctl_card_info(handle, info)) < 0)
00060 {
00061 printf("AudioALSA::list_devices (%i): %s\n", card, snd_strerror(err));
00062 snd_ctl_close(handle);
00063 continue;
00064 }
00065
00066 dev = -1;
00067
00068 while(1)
00069 {
00070 unsigned int count;
00071 if(snd_ctl_pcm_next_device(handle, &dev) < 0)
00072 printf("AudioALSA::list_devices: snd_ctl_pcm_next_device\n");
00073
00074 if (dev < 0)
00075 break;
00076
00077 snd_pcm_info_set_device(pcminfo, dev);
00078 snd_pcm_info_set_subdevice(pcminfo, 0);
00079 snd_pcm_info_set_stream(pcminfo, stream);
00080
00081 if((err = snd_ctl_pcm_info(handle, pcminfo)) < 0)
00082 {
00083 if(err != -ENOENT)
00084 printf("AudioALSA::list_devices (%i): %s\n", card, snd_strerror(err));
00085 continue;
00086 }
00087
00088 if(pcm_title)
00089 {
00090 sprintf(string, "plughw:%d,%d", card, dev);
00091
00092 }
00093 else
00094 {
00095 sprintf(string, "%s #%d",
00096 snd_ctl_card_info_get_name(info),
00097 dev);
00098 }
00099
00100 char *result = devices->append(new char[strlen(string) + 1]);
00101 strcpy(result, string);
00102 }
00103
00104 snd_ctl_close(handle);
00105 }
00106
00107
00108
00109 }
00110
00111 void AudioALSA::translate_name(char *output, char *input)
00112 {
00113 ArrayList<char*> titles;
00114 ArrayList<char*> pcm_titles;
00115
00116 list_devices(&titles, 0);
00117 list_devices(&pcm_titles, 1);
00118
00119 sprintf(output, "default");
00120 for(int i = 0; i < titles.total; i++)
00121 {
00122
00123 if(!strcasecmp(titles.values[i], input))
00124 {
00125 strcpy(output, pcm_titles.values[i]);
00126 break;
00127 }
00128 }
00129
00130 titles.remove_all_objects();
00131 pcm_titles.remove_all_objects();
00132 }
00133
00134 snd_pcm_format_t AudioALSA::translate_format(int format)
00135 {
00136 switch(format)
00137 {
00138 case 8:
00139 return SND_PCM_FORMAT_S8;
00140 break;
00141 case 16:
00142 return SND_PCM_FORMAT_S16_LE;
00143 break;
00144 case 24:
00145 return SND_PCM_FORMAT_S24_LE;
00146 break;
00147 case 32:
00148 return SND_PCM_FORMAT_S32_LE;
00149 break;
00150 }
00151 }
00152
00153 void AudioALSA::set_params(snd_pcm_t *dsp,
00154 int channels,
00155 int bits,
00156 int samplerate,
00157 int samples)
00158 {
00159 snd_pcm_hw_params_t *params;
00160 snd_pcm_sw_params_t *swparams;
00161 int err;
00162
00163 snd_pcm_hw_params_alloca(¶ms);
00164 snd_pcm_sw_params_alloca(&swparams);
00165 err = snd_pcm_hw_params_any(dsp, params);
00166
00167 if (err < 0)
00168 {
00169 printf("AudioALSA::set_params: no PCM configurations available\n");
00170 return;
00171 }
00172
00173 snd_pcm_hw_params_set_access(dsp,
00174 params,
00175 SND_PCM_ACCESS_RW_INTERLEAVED);
00176 snd_pcm_hw_params_set_format(dsp,
00177 params,
00178 translate_format(bits));
00179 snd_pcm_hw_params_set_channels(dsp,
00180 params,
00181 channels);
00182 snd_pcm_hw_params_set_rate_near(dsp,
00183 params,
00184 (unsigned int*)&samplerate,
00185 (int*)0);
00186
00187
00188 int buffer_time;
00189 int period_time;
00190 if(device->r)
00191 {
00192 buffer_time = 10000000;
00193 period_time = (int)((int64_t)samples * 1000000 / samplerate);
00194 }
00195 else
00196 {
00197 buffer_time = (int)((int64_t)samples * 1000000 * 2 / samplerate + 0.5);
00198 period_time = samples * samplerate / 1000000;
00199 }
00200
00201
00202
00203 snd_pcm_hw_params_set_buffer_time_near(dsp,
00204 params,
00205 (unsigned int*)&buffer_time,
00206 (int*)0);
00207 snd_pcm_hw_params_set_period_time_near(dsp,
00208 params,
00209 (unsigned int*)&period_time,
00210 (int*)0);
00211
00212 err = snd_pcm_hw_params(dsp, params);
00213 if(err < 0)
00214 {
00215 printf("AudioALSA::set_params: hw_params failed\n");
00216 return;
00217 }
00218
00219 snd_pcm_uframes_t chunk_size = 1024;
00220 snd_pcm_uframes_t buffer_size = 262144;
00221 snd_pcm_hw_params_get_period_size(params, &chunk_size, 0);
00222 snd_pcm_hw_params_get_buffer_size(params, &buffer_size);
00223
00224
00225 snd_pcm_sw_params_current(dsp, swparams);
00226 size_t xfer_align = 1 ;
00227 unsigned int sleep_min = 0;
00228 err = snd_pcm_sw_params_set_sleep_min(dsp, swparams, sleep_min);
00229 int n = chunk_size;
00230 err = snd_pcm_sw_params_set_avail_min(dsp, swparams, n);
00231 err = snd_pcm_sw_params_set_xfer_align(dsp, swparams, xfer_align);
00232 if(snd_pcm_sw_params(dsp, swparams) < 0)
00233 {
00234 printf("AudioALSA::set_params: snd_pcm_sw_params failed\n");
00235 }
00236
00237 device->device_buffer = samples * bits / 8 * channels;
00238
00239
00240
00241
00242
00243 }
00244
00245 int AudioALSA::open_input()
00246 {
00247 char pcm_name[BCTEXTLEN];
00248 snd_pcm_stream_t stream = SND_PCM_STREAM_CAPTURE;
00249 int open_mode = 0;
00250 int err;
00251
00252 device->in_channels = device->in_config->alsa_in_channels;
00253 device->in_bits = device->in_config->alsa_in_bits;
00254
00255 translate_name(pcm_name, device->in_config->alsa_in_device);
00256
00257 err = snd_pcm_open(&dsp_in, pcm_name, stream, open_mode);
00258
00259 if(err < 0)
00260 {
00261 printf("AudioALSA::open_input: %s\n", snd_strerror(err));
00262 return 1;
00263 }
00264
00265 set_params(dsp_in,
00266 device->in_config->alsa_in_channels,
00267 device->in_config->alsa_in_bits,
00268 device->in_samplerate,
00269 device->in_samples);
00270
00271 return 0;
00272 }
00273
00274 int AudioALSA::open_output()
00275 {
00276 char pcm_name[BCTEXTLEN];
00277 snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK;
00278 int open_mode = 0;
00279 int err;
00280
00281 device->out_channels = device->out_config->alsa_out_channels;
00282 device->out_bits = device->out_config->alsa_out_bits;
00283
00284 translate_name(pcm_name, device->out_config->alsa_out_device);
00285
00286 err = snd_pcm_open(&dsp_out, pcm_name, stream, open_mode);
00287
00288 if(err < 0)
00289 {
00290 printf("AudioALSA::open_output %s: %s\n", pcm_name, snd_strerror(err));
00291 return 1;
00292 }
00293
00294 set_params(dsp_out,
00295 device->out_config->alsa_out_channels,
00296 device->out_config->alsa_out_bits,
00297 device->out_samplerate,
00298 device->out_samples);
00299 timer->update();
00300 return 0;
00301 }
00302
00303 int AudioALSA::open_duplex()
00304 {
00305
00306 return 0;
00307 }
00308
00309 int AudioALSA::close_output()
00310 {
00311 if(device->w)
00312 {
00313 snd_pcm_close(dsp_out);
00314 }
00315 return 0;
00316 }
00317
00318 int AudioALSA::close_input()
00319 {
00320 if(device->r)
00321 {
00322
00323 snd_pcm_drop(dsp_in);
00324 snd_pcm_drain(dsp_in);
00325 snd_pcm_close(dsp_in);
00326 }
00327 return 0;
00328 }
00329
00330 int AudioALSA::close_all()
00331 {
00332 close_input();
00333 close_output();
00334 if(device->d)
00335 {
00336 snd_pcm_close(dsp_duplex);
00337 }
00338 samples_written = 0;
00339 delay = 0;
00340 interrupted = 0;
00341 }
00342
00343
00344 int64_t AudioALSA::device_position()
00345 {
00346 timer_lock->lock("AudioALSA::device_position");
00347 int64_t result = samples_written +
00348 timer->get_scaled_difference(device->out_samplerate) -
00349 delay;
00350
00351
00352
00353
00354
00355 timer_lock->unlock();
00356 return result;
00357 }
00358
00359 int AudioALSA::read_buffer(char *buffer, int size)
00360 {
00361
00362 int attempts = 0;
00363 int done = 0;
00364 while(attempts < 1 && !done)
00365 {
00366 if(snd_pcm_readi(get_input(),
00367 buffer,
00368 size / (device->in_bits / 8) / device->in_channels) < 0)
00369 {
00370 printf("AudioALSA::read_buffer overrun at sample %lld\n",
00371 device->total_samples_read);
00372
00373 close_input();
00374 open_input();
00375 attempts++;
00376 }
00377 else
00378 done = 1;
00379 }
00380 return 0;
00381 }
00382
00383 int AudioALSA::write_buffer(char *buffer, int size)
00384 {
00385
00386 int attempts = 0;
00387 int done = 0;
00388 int samples = size / (device->out_bits / 8) / device->out_channels;
00389 while(attempts < 2 && !done && !interrupted)
00390 {
00391
00392
00393 snd_pcm_sframes_t delay;
00394 snd_pcm_delay(get_output(), &delay);
00395 snd_pcm_avail_update(get_output());
00396
00397 device->Thread::enable_cancel();
00398 if(snd_pcm_writei(get_output(),
00399 buffer,
00400 samples) < 0)
00401 {
00402 device->Thread::disable_cancel();
00403 printf("AudioALSA::write_buffer underrun at sample %lld\n",
00404 device->current_position());
00405
00406 close_output();
00407 open_output();
00408 attempts++;
00409 }
00410 else
00411 {
00412 device->Thread::disable_cancel();
00413 done = 1;
00414 }
00415 }
00416
00417 if(done)
00418 {
00419 timer_lock->lock("AudioALSA::write_buffer");
00420 this->delay = delay;
00421 timer->update();
00422 samples_written += samples;
00423 timer_lock->unlock();
00424 }
00425 return 0;
00426 }
00427
00428 int AudioALSA::flush_device()
00429 {
00430 if(get_output()) snd_pcm_drain(get_output());
00431 return 0;
00432 }
00433
00434 int AudioALSA::interrupt_playback()
00435 {
00436 if(get_output())
00437 {
00438 interrupted = 1;
00439
00440
00441 if(!device->out_config->interrupt_workaround)
00442 snd_pcm_drop(get_output());
00443
00444
00445
00446
00447
00448
00449
00450 }
00451 return 0;
00452 }
00453
00454
00455 snd_pcm_t* AudioALSA::get_output()
00456 {
00457 if(device->w) return dsp_out;
00458 else
00459 if(device->d) return dsp_duplex;
00460 return 0;
00461 }
00462
00463 snd_pcm_t* AudioALSA::get_input()
00464 {
00465 if(device->r) return dsp_in;
00466 else
00467 if(device->d) return dsp_duplex;
00468 return 0;
00469 }
00470
00471 #endif