/*
Copyright (C) 2005-2007 Michel de Boer <michel@twinklephone.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "audio_device.h"
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>
#include "sys_settings.h"
#include "translator.h"
#include "log.h"
#include "userintf.h"
#include "util.h"
#include "audits/memman.h"
#ifdef HAVE_LIBASOUND
#include <alsa/asoundlib.h>
#endif
t_audio_io* t_audio_io::open(const t_audio_device& dev, bool playback, bool capture, bool blocking, int channels, t_audio_sampleformat format, int sample_rate, bool short_latency)
{
t_audio_io* aio;
if(dev.type == t_audio_device::OSS) {
aio = new t_oss_io();
MEMMAN_NEW(aio);
#ifdef HAVE_LIBASOUND
} else if (dev.type == t_audio_device::ALSA) {
aio = new t_alsa_io();
MEMMAN_NEW(aio);
#endif
} else {
string msg("Audio device not implemented");
log_file->write_report(msg, "t_audio_io::open",
LOG_NORMAL, LOG_CRITICAL);
return 0;
}
if (aio->open(dev.device, playback, capture, blocking, channels, format,
sample_rate, short_latency)) {
return aio;
} else {
string msg("Open audio device failed");
log_file->write_report(msg, "t_audio_io::open",
LOG_NORMAL, LOG_CRITICAL);
MEMMAN_DELETE(aio);
delete aio;
return 0L;
}
}
bool t_audio_io::validate(const t_audio_device& dev, bool playback, bool capture) {
t_audio_io *aio = open(dev, playback, capture, false, 1, SAMPLEFORMAT_S16, 8000, true);
if (aio) {
MEMMAN_DELETE(aio);
delete aio;
return true;
}
return false;
}
t_audio_io::~t_audio_io() {}
int t_audio_io::get_sample_rate(void) const {
return _sample_rate;
}
bool t_audio_io::open(const string& device, bool playback, bool capture, bool blocking, int channels, t_audio_sampleformat format, int sample_rate, bool short_latency)
{
_sample_rate = sample_rate;
return true;
}
t_oss_io::t_oss_io() : fd(-1), rec_buffersize(0), play_buffersize(0) {
}
t_oss_io::~t_oss_io()
{
if (fd > 0) {
int arg = 0;
ioctl(fd, SNDCTL_DSP_RESET, &arg);
close(fd);
}
fd = -1;
}
bool t_oss_io::open(const string& device, bool playback, bool capture, bool blocking, int channels, t_audio_sampleformat format, int sample_rate, bool short_latency)
{
t_audio_io::open(device, playback, capture, blocking, channels, format, sample_rate,
short_latency);
int mode = 0;
int status;
log_file->write_header("t_oss_io::open", LOG_NORMAL);
log_file->write_raw("Opening OSS device: ");
log_file->write_raw(device);
log_file->write_endl();
if (playback) log_file->write_raw("play\n");
if (capture) log_file->write_raw("capture\n");
log_file->write_footer();
assert (playback || capture);
if (playback && capture) mode |= O_RDWR;
else if (playback) mode |= O_WRONLY;
else if (capture) mode |= O_RDONLY;
// On some systems opening the audio devices blocks if another
// process or thread has opened it already. To prevent a deadlock
// first try to open the device in non-blocking mode.
// If the device is still open by another twinkle thread then that
// is a bug, but this way at least non deadlock is caused.
if(blocking) {
fd = ::open(device.c_str(), mode | O_NONBLOCK);
if (fd == -1) {
string msg("OSS audio device open failed: ");
msg += get_error_str(errno);
log_file->write_report(msg, "t_oss_io::open",
LOG_NORMAL, LOG_CRITICAL);
return false;
} else {
close(fd);
fd = -1;
}
} else mode |= O_NONBLOCK;
fd = ::open(device.c_str(), mode);
if (fd < 0) {
string msg("OSS audio device open failed: ");
msg += get_error_str(errno);
log_file->write_report(msg, "t_oss_io::open",
LOG_NORMAL, LOG_CRITICAL);
return false;
}
// Full duplex
if (playback && capture) {
status = ioctl(fd, SNDCTL_DSP_SETDUPLEX, 0);
if (status == -1) {
string msg("SNDCTL_DSP_SETDUPLEX ioctl failed: ");
msg += get_error_str(errno);
log_file->write_report(msg, "t_oss_io::open",
LOG_NORMAL, LOG_CRITICAL);
ui->cb_display_msg(TRANSLATE("Sound card cannot be set to full duplex."),
MSG_CRITICAL);
close(fd);
return false;
}
}
// Set fragment size
int arg;
if (short_latency) {
switch (sys_config->get_oss_fragment_size()) {
case 16:
arg = 0x00ff0004; // 255 buffers of 2^4 bytes each
break;
case 32:
arg = 0x00ff0005; // 255 buffers of 2^5 bytes each
break;
case 64:
arg = 0x00ff0006; // 255 buffers of 2^5 bytes each
break;
case 128:
arg = 0x00ff0007; // 255 buffers of 2^7 bytes each
break;
case 256:
arg = 0x00ff0008; // 255 buffers of 2^8 bytes each
break;
default:
arg = 0x00ff0007; // 255 buffers of 2^7 bytes each
}
} else {
arg = 0x00ff000a; // 255 buffers of 2^10 bytes each
}
status = ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &arg);
if (status == -1) {
string msg("SNDCTL_DSP_FRAGMENT ioctl failed: ");
msg += get_error_str(errno);
log_file->write_report(msg, "t_oss_io::open",
LOG_NORMAL, LOG_CRITICAL);
ui->cb_display_msg(TRANSLATE("Cannot set buffer size on sound card."),
MSG_CRITICAL);
close(fd);
return false;
}
// Channels
arg = channels;
status = ioctl(fd, SNDCTL_DSP_CHANNELS, &arg);
if (status == -1) {
string msg("SNDCTL_DSP_CHANNELS ioctl failed: ");
msg += get_error_str(errno);
log_file->write_report(msg, "t_oss_io::open",
LOG_NORMAL, LOG_CRITICAL);
msg = TRANSLATE("Sound card cannot be set to %1 channels.");
msg = replace_first(msg, "%1", int2str(channels));
ui->cb_display_msg(msg, MSG_CRITICAL);
return false;
}
if (arg != channels) {
log_file->write_report("Unable to set channels",
"t_oss_io::open",
LOG_NORMAL, LOG_CRITICAL);
string msg = "Sound card cannot be set to ";
msg = TRANSLATE("Sound card cannot be set to %1 channels.");
msg = replace_first(msg, "%1", int2str(channels));
ui->cb_display_msg(msg, MSG_CRITICAL);
return false;
}
// Sample format
int fmt;
switch (format) {
case SAMPLEFORMAT_S16:
#ifdef WORDS_BIGENDIAN
fmt = AFMT_S16_BE;
#else
fmt = AFMT_S16_LE;
#endif
break;
case SAMPLEFORMAT_U16:
#ifdef WORDS_BIGENDIAN
fmt = AFMT_U16_BE;
#else
fmt = AFMT_U16_LE;
#endif
break;
case SAMPLEFORMAT_S8:
fmt = AFMT_S8;
break;
case SAMPLEFORMAT_U8:
fmt = AFMT_U8;
break;
default:
fmt = 0; // fail
}
arg = fmt;
status = ioctl(fd, SNDCTL_DSP_SETFMT, &arg);
if (status == -1) {
string msg("SNDCTL_DSP_SETFMT ioctl failed: ");
msg += get_error_str(errno);
log_file->write_report(msg, "t_oss_io::open",
LOG_NORMAL, LOG_CRITICAL);
ui->cb_display_msg(TRANSLATE("Cannot set sound card to 16 bits recording."),
MSG_CRITICAL);
return false;
}
arg = fmt;
status = ioctl(fd, SOUND_PCM_WRITE_BITS, &arg);
if (status == -1) {
string msg("SOUND_PCM_WRITE_BITS ioctl failed: ");
msg += get_error_str(errno);
log_file->write_report(msg, "t_oss_io::open",
LOG_NORMAL, LOG_CRITICAL);
ui->cb_display_msg(TRANSLATE("Cannot set sound card to 16 bits playing."),
MSG_CRITICAL);
return false;
}
// Sample rate
arg = sample_rate;
status = ioctl(fd, SNDCTL_DSP_SPEED, &arg);
if (status == -1) {
string msg("SNDCTL_DSP_SPEED ioctl failed: ");
msg += get_error_str(errno);
log_file->write_report(msg, "t_oss_io::open",
LOG_NORMAL, LOG_CRITICAL);
msg = TRANSLATE("Cannot set sound card sample rate to %1");
msg = replace_first(msg, "%1", int2str(sample_rate));
ui->cb_display_msg(msg, MSG_CRITICAL);
return false;
}
play_buffersize = rec_buffersize = 0;
if (playback) play_buffersize = get_buffer_space(false);
if (capture) rec_buffersize = get_buffer_space(true);
return true;
}
void t_oss_io::enable(bool enable_playback, bool enable_recording) {
int arg, status;
arg = enable_recording ? PCM_ENABLE_INPUT : 0;
arg |= enable_playback ? PCM_ENABLE_OUTPUT : 0;
status = ioctl(fd, SNDCTL_DSP_SETTRIGGER, &arg);
if (status == -1) {
string msg("SNDCTL_DSP_SETTRIGGER ioctl failed: ");
msg += get_error_str(errno);
log_file->write_report(msg, "t_oss_io::enable",
LOG_NORMAL, LOG_CRITICAL);
}
}
void t_oss_io::flush(bool playback_buffer, bool recording_buffer) {
for (int i = 0; i < 2; i++) {
// i == 0: flush playback buffer, 1: flush recording buffer
if (i == 0 && playback_buffer || i == 1 && recording_buffer) {
int skip_bytes = ( (i==0) ? play_buffersize :
rec_buffersize) - get_buffer_space(i == 1);
if(skip_bytes <= 0) continue;
unsigned char *trash = new unsigned char[skip_bytes];
MEMMAN_NEW_ARRAY(trash);
read(trash, skip_bytes);
MEMMAN_DELETE_ARRAY(trash);
delete [] trash;
}
}
}
int t_oss_io::get_buffer_space(bool is_recording_buffer)
{
audio_buf_info dsp_info;
int status = ioctl(fd, is_recording_buffer ? SNDCTL_DSP_GETISPACE :
SNDCTL_DSP_GETOSPACE, &dsp_info);
if (status == -1) return 0;
return dsp_info.bytes;
}
int t_oss_io::get_buffer_size(bool is_recording_buffer)
{
if (is_recording_buffer) return rec_buffersize;
else return play_buffersize;
}
int t_oss_io::read(unsigned char* buf, int len) {
return ::read(fd, buf, len);
}
int t_oss_io::write(const unsigned char* buf, int len) {
return ::write(fd, buf, len);
}
#ifdef HAVE_LIBASOUND
t_alsa_io::t_alsa_io() : pcm_play_ptr(0), pcm_rec_ptr(0), play_framesize(1), rec_framesize(1),
play_buffersize(0), rec_buffersize(0) {
}
t_alsa_io::~t_alsa_io() {
if (pcm_play_ptr) {
log_file->write_header("t_alsa_io::~t_alsa_io", LOG_NORMAL, LOG_DEBUG);
log_file->write_raw("snd_pcm_close, handle = ");
log_file->write_raw(ptr2str(pcm_play_ptr));
log_file->write_endl();
log_file->write_footer();
// Without the snd_pcm_hw_free, snd_pcm_close sometimes fails.
snd_pcm_hw_free(pcm_play_ptr);
snd_pcm_close(pcm_play_ptr);
pcm_play_ptr = 0;
}
if (pcm_rec_ptr) {
log_file->write_header("t_alsa_io::~t_alsa_io", LOG_NORMAL, LOG_DEBUG);
log_file->write_raw("snd_pcm_close, handle = ");
log_file->write_raw(ptr2str(pcm_rec_ptr));
log_file->write_endl();
log_file->write_footer();
snd_pcm_hw_free(pcm_rec_ptr);
snd_pcm_close(pcm_rec_ptr);
pcm_rec_ptr = 0;
}
}
bool t_alsa_io::open(const string& device, bool playback, bool capture, bool blocking, int channels, t_audio_sampleformat format, int sample_rate, bool short_latency)
{
t_audio_io::open(device, playback, capture, blocking, channels, format, sample_rate,
short_latency);
int mode = 0;
int status;
string msg;
this->short_latency = short_latency;
const char* dev = device.c_str();
if(dev[0] == 0) dev = "default";
log_file->write_header("t_alsa_io::open", LOG_NORMAL);
log_file->write_raw("Opening ALSA device: ");
log_file->write_raw(dev);
log_file->write_endl();
if (playback) log_file->write_raw("play\n");
if (capture) log_file->write_raw("capture\n");
log_file->write_footer();
if (playback && capture) {
// Full duplex: Perform the operation in two steps
if (!open(device, true, false, blocking, channels, format,
sample_rate, short_latency))
return false;
if (!open(device, false, true, blocking, channels, format,
sample_rate, short_latency))
return false;
return true;
}
snd_pcm_t* pcm_ptr;
#define HANDLE_ALSA_ERROR(func) string msg(func); msg += " failed: "; msg += snd_strerror(err); \
log_file->write_report(msg, "t_alsa_io::open", LOG_NORMAL, LOG_CRITICAL); msg = TRANSLATE("Opening ALSA driver failed") + ": " + msg; \
ui->cb_display_msg(msg, MSG_CRITICAL); if(pcm_ptr) snd_pcm_close(pcm_ptr); return false;
mode = SND_PCM_NONBLOCK;
open_again:
int err = snd_pcm_open(&pcm_ptr, dev, playback ? SND_PCM_STREAM_PLAYBACK :
SND_PCM_STREAM_CAPTURE, mode);
if (err < 0) {
string msg("snd_pcm_open failed: ");
msg += snd_strerror(err);
log_file->write_report(msg, "t_alsa_io::open",
LOG_NORMAL, LOG_CRITICAL);
msg = "Cannot open ALSA driver for PCM ";
if (playback) {
msg = TRANSLATE("Cannot open ALSA driver for PCM playback");
} else {
msg = TRANSLATE("Cannot open ALSA driver for PCM capture");
}
msg += ": ";
msg += snd_strerror(err);
ui->cb_display_msg(msg, MSG_CRITICAL);
return false;
}
log_file->write_header("t_alsa_io::open", LOG_NORMAL, LOG_DEBUG);
log_file->write_raw("snd_pcm_open succeeded, handle = ");
log_file->write_raw(ptr2str(pcm_ptr));
log_file->write_endl();
log_file->write_footer();
if (blocking && mode & SND_PCM_NONBLOCK) {
log_file->write_header("t_alsa_io::open", LOG_NORMAL, LOG_DEBUG);
log_file->write_raw("snd_pcm_close, handle = ");
log_file->write_raw(ptr2str(pcm_ptr));
log_file->write_endl();
log_file->write_footer();
// Do not call snd_pcm_hw_free here. There is no hardware to release
// yet. On ALSA 1.0.9 it is fine to call snd_pcm_hw_free here. But
// ALSA 1.0.6 gives an assert.
snd_pcm_close(pcm_ptr);
mode &= ~SND_PCM_NONBLOCK;
goto open_again;
}
snd_pcm_hw_params_t *hw_params;
snd_pcm_sw_params_t *sw_params;
// Set HW params
if ((err = snd_pcm_hw_params_malloc (&hw_params)) < 0) {
HANDLE_ALSA_ERROR("snd_pcm_hw_params_malloc");
}
MEMMAN_NEW(hw_params);
if ((err = snd_pcm_hw_params_any (pcm_ptr, hw_params)) < 0) {
HANDLE_ALSA_ERROR("snd_pcm_hw_params_any");
}
if ((err = snd_pcm_hw_params_set_access (pcm_ptr, hw_params,
SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
{
HANDLE_ALSA_ERROR("snd_pcm_hw_params_set_access");
}
snd_pcm_format_t fmt;
int sample_bits;
switch (format) {
case SAMPLEFORMAT_S16:
#ifdef WORDS_BIGENDIAN
fmt = SND_PCM_FORMAT_S16_BE;
#else
fmt = SND_PCM_FORMAT_S16_LE;
#endif
sample_bits = 16;
break;
case SAMPLEFORMAT_U16:
#ifdef WORDS_BIGENDIAN
fmt = SND_PCM_FORMAT_U16_BE;
#else
fmt = SND_PCM_FORMAT_U16_LE;
#endif
sample_bits = 16;
break;
case SAMPLEFORMAT_S8:
fmt = SND_PCM_FORMAT_S8;
sample_bits = 8;
break;
case SAMPLEFORMAT_U8:
fmt = SND_PCM_FORMAT_U8;
sample_bits = 8;
break;
default:
{HANDLE_ALSA_ERROR("invalid sample format");}
}
if ((err = snd_pcm_hw_params_set_format (pcm_ptr, hw_params, fmt)) < 0) {
string s("snd_pcm_hw_params_set_format(");
s += int2str(fmt);
s += ")";
HANDLE_ALSA_ERROR(s);
}
// Some sound cards do not support the exact sample rate specified in the
// wav files. Still we first try to set the exact sample rate instead of
// specifying the 3rd parameter as -1 to choose an approximate.
// For some reason on my first sound card that supports the exact rate,
// I get mono sound when the -1 parameter is specified.
if ((err = snd_pcm_hw_params_set_rate (pcm_ptr, hw_params, sample_rate, 0)) < 0) {
msg = "Cannot set soundcard to exact sample rate of ";
msg += int2str(sample_rate);
msg += ".\nThe card will choose a lower approximate rate.";
log_file->write_report(msg, "t_alsa_io::open", LOG_NORMAL, LOG_WARNING);
if ((err = snd_pcm_hw_params_set_rate (pcm_ptr, hw_params, sample_rate, -1)) < 0) {
string s("snd_pcm_hw_params_set_rate(");
s += int2str(sample_rate);
s += ")";
HANDLE_ALSA_ERROR(s);
}
}
// Read back card rate for reporting in the log file.
unsigned int card_rate;
int card_dir;
snd_pcm_hw_params_get_rate(hw_params, &card_rate, &card_dir);
if ((err = snd_pcm_hw_params_set_channels (pcm_ptr, hw_params, channels)) < 0) {
string s("snd_pcm_hw_params_set_channels(");
s += int2str(channels);
s += ")";
HANDLE_ALSA_ERROR(s);
}
// Note: The buffersize is in FRAMES, not BYTES (one frame = sizeof(sample) * channels)
snd_pcm_uframes_t buffersize;
unsigned int periods = 8; // Double buffering
int dir = 1;
// Set the size of one period in samples
if (short_latency) {
if (playback) {
buffersize = sys_config->get_alsa_play_period_size();
} else {
buffersize = sys_config->get_alsa_capture_period_size();
}
} else {
buffersize = 1024;
}
if ((err = snd_pcm_hw_params_set_period_size_near (pcm_ptr, hw_params,
&buffersize, &dir)) < 0)
{
HANDLE_ALSA_ERROR("snd_pcm_hw_params_set_period_size_near");
}
// The number of periods determines the ALSA application buffer size.
// This size must be larger than the jitter buffer.
// TODO: use some more sophisticated algorithm here: read back the period
// size and calculate the number of periods needed (only in the
// short latency case)?
if (buffersize <= 64) {
periods *= 8;
} else if (buffersize <= 256) {
periods *= 4;
}
dir = 1;
if ((err = snd_pcm_hw_params_set_periods(pcm_ptr, hw_params, periods, dir)) < 0) {
if ((err = snd_pcm_hw_params_set_periods_near(pcm_ptr, hw_params,
&periods, &dir)) < 0)
{
HANDLE_ALSA_ERROR("snd_pcm_hw_params_set_periods");
}
}
if ((err = snd_pcm_hw_params (pcm_ptr, hw_params)) < 0) {
HANDLE_ALSA_ERROR("snd_pcm_hw_params");
}
// Find out if the sound card supports pause functionality
can_pause = (snd_pcm_hw_params_can_pause(hw_params) == 1);
MEMMAN_DELETE(hw_params);
snd_pcm_hw_params_free(hw_params);
// Set SW params
if ((err = snd_pcm_sw_params_malloc(&sw_params)) < 0) {
HANDLE_ALSA_ERROR("snd_pcm_sw_params_malloc");
}
MEMMAN_NEW(sw_params);
if ((err = snd_pcm_sw_params_current (pcm_ptr, sw_params)) < 0) {
HANDLE_ALSA_ERROR("snd_pcm_sw_params_current");
}
if ((err = snd_pcm_sw_params (pcm_ptr, sw_params)) < 0) {
HANDLE_ALSA_ERROR("snd_pcm_sw_params");
}
MEMMAN_DELETE(sw_params);
snd_pcm_sw_params_free(sw_params);
if ((err = snd_pcm_prepare (pcm_ptr)) < 0) {
HANDLE_ALSA_ERROR("snd_pcm_prepare");
}
if (playback) {
pcm_play_ptr = pcm_ptr;
play_framesize = (sample_bits / 8) * channels;
play_buffersize = (int)buffersize * periods * play_framesize;
play_periods = periods;
log_file->write_header("t_alsa_io::open", LOG_NORMAL, LOG_DEBUG);
log_file->write_raw("ALSA playback buffer settings.\n");
log_file->write_raw("Rate = ");
log_file->write_raw(card_rate);
log_file->write_raw(" frames/sec\n");
log_file->write_raw("Frame size = ");
log_file->write_raw(play_framesize);
log_file->write_raw(" bytes\n");
log_file->write_raw("Periods = ");
log_file->write_raw(play_periods);
log_file->write_endl();
log_file->write_raw("Period size = ");
log_file->write_raw(buffersize * play_framesize);
log_file->write_raw(" bytes\n");
log_file->write_raw("Buffer size = ");
log_file->write_raw(play_buffersize);
log_file->write_raw(" bytes\n");
log_file->write_raw("Can pause: ");
log_file->write_bool(can_pause);
log_file->write_endl();
log_file->write_footer();
} else {
// Since audio_rx checks buffer before reading, start manually
if ((err = snd_pcm_start(pcm_ptr)) < 0) {
HANDLE_ALSA_ERROR("snd_pcm_start");
}
pcm_rec_ptr = pcm_ptr;
rec_framesize = (sample_bits / 8) * channels;
rec_buffersize = (int)buffersize * periods * rec_framesize;
rec_periods = periods;
// HACK: snd_pcm_delay seems not to work for the dsnoop device.
// This code determines if snd_pcm is working. As the capture
// device just started, it should return zero or a small number.
// So if it returns that more than half of the buffer is filled
// already, it seems broken.
rec_delay_broken = false;
if (get_buffer_space(true) > rec_buffersize / 2) {
rec_delay_broken = true;
log_file->write_report(
"snd_pcm_delay does not work for capture buffer.",
"t_alsa_io::open", LOG_NORMAL, LOG_DEBUG);
}
log_file->write_header("t_alsa_io::open", LOG_NORMAL, LOG_DEBUG);
log_file->write_raw("ALSA capture buffer settings.\n");
log_file->write_raw("Rate = ");
log_file->write_raw(card_rate);
log_file->write_raw(" frames/sec\n");
log_file->write_raw("Frame size = ");
log_file->write_raw(rec_framesize);
log_file->write_raw(" bytes\n");
log_file->write_raw("Periods = ");
log_file->write_raw(rec_periods);
log_file->write_endl();
log_file->write_raw("Period size = ");
log_file->write_raw(buffersize * rec_framesize);
log_file->write_raw(" bytes\n");
log_file->write_raw("Buffer size = ");
log_file->write_raw(rec_buffersize);
log_file->write_raw(" bytes\n");
log_file->write_raw("Can pause: ");
log_file->write_bool(can_pause);
log_file->write_endl();
log_file->write_footer();
}
return true;
#undef HANDLE_ALSA_ERROR
}
void t_alsa_io::enable(bool enable_playback, bool enable_recording) {
if (!can_pause) return;
if (pcm_play_ptr) {
snd_pcm_pause(pcm_play_ptr, (int)enable_playback);
}
if (pcm_rec_ptr) {
snd_pcm_pause(pcm_rec_ptr, (int)enable_recording);
}
}
void t_alsa_io::flush(bool playback_buffer, bool recording_buffer) {
if (playback_buffer && pcm_play_ptr) {
// snd_pcm_reset(pcm_play_ptr);
snd_pcm_drop(pcm_play_ptr);
snd_pcm_prepare(pcm_play_ptr);
snd_pcm_start(pcm_play_ptr);
}
if (recording_buffer && pcm_rec_ptr) {
// For some obscure reason snd_pcm_reset causes the CPU
// load to rise to 99.9% when playing and capturing is
// done on the same sound card.
// Therefor snd_pcm_reset is replaced by functions to
// stop the card, drop samples and start again.
// snd_pcm_reset(pcm_rec_ptr);
snd_pcm_drop(pcm_rec_ptr);
snd_pcm_prepare(pcm_rec_ptr);
snd_pcm_start(pcm_rec_ptr);
}
}
int t_alsa_io::get_buffer_space(bool is_recording_buffer) {
int rv;
int err;
snd_pcm_sframes_t delay;
snd_pcm_status_t* status;
snd_pcm_status_alloca(&status);
if(is_recording_buffer) {
if(!pcm_rec_ptr) return 0;
// This does not work as snd_pcm_status_get_avail_max does not return
// accurate results.
// snd_pcm_status(pcm_rec_ptr, status);
// rv = rec_framesize * snd_pcm_status_get_avail_max(status);
snd_pcm_hwsync(pcm_rec_ptr);
if (err = snd_pcm_delay(pcm_rec_ptr, &delay) < 0) {
string msg = "snd_pcm_delay for capture buffer failed: ";
msg += snd_strerror(err);
log_file->write_report(msg, "t_alsa_io::get_buffer_space",
LOG_NORMAL, LOG_DEBUG);
delay = 0;
snd_pcm_prepare(pcm_rec_ptr);
}
if (rec_delay_broken) {
rv = 0; // there is no way to get an accurate number
} else {
rv = int(delay * rec_framesize);
}
if (rv > rec_buffersize) {
rv = rec_buffersize; // capture buffer overrun
snd_pcm_prepare(pcm_rec_ptr);
}
} else {
if(!pcm_play_ptr) return 0;
snd_pcm_status(pcm_play_ptr, status);
rv = play_framesize * snd_pcm_status_get_avail_max(status);
if (rv > play_buffersize) {
rv = play_buffersize; // playback buffer underrun
snd_pcm_prepare(pcm_play_ptr);
}
}
return rv;
}
int t_alsa_io::get_buffer_size(bool is_recording_buffer)
{
if (is_recording_buffer) return rec_buffersize;
else return play_buffersize;
}
int t_alsa_io::read(unsigned char* buf, int len) {
string msg;
if (!pcm_rec_ptr) {
log_file->write_report("Illegal pcm_rec_prt.",
"t_alsa_io::read", LOG_NORMAL, LOG_CRITICAL);
return -1;
}
int len_frames = len / rec_framesize;
int read_frames = 0;
for(;;) {
int read = snd_pcm_readi(pcm_rec_ptr, buf, len_frames);
if (read == -EPIPE) {
msg = "Capture buffer overrun.";
log_file->write_report(msg, "t_alsa_io::read", LOG_NORMAL, LOG_DEBUG);
snd_pcm_prepare(pcm_rec_ptr);
snd_pcm_start(pcm_rec_ptr);
continue;
} else if (read <= 0) {
msg = "PCM read error: ";
msg += snd_strerror(read);
log_file->write_report(msg, "t_alsa_io::read", LOG_NORMAL, LOG_DEBUG);
return -1;
} else if (read < len_frames) {
buf += rec_framesize * read;
len_frames -= read;
read_frames += read;
continue;
}
return (read_frames + read) * rec_framesize;
}
}
int t_alsa_io::write(const unsigned char* buf, int len) {
int len_frames = len / play_framesize;
int frames_written = 0;
string msg;
for (;;) {
if(!pcm_play_ptr) return -1;
int written = snd_pcm_writei(pcm_play_ptr, buf, len_frames);
if (written == -EPIPE) {
msg = "Playback buffer underrun.";
log_file->write_report(msg, "t_alsa_io::write", LOG_NORMAL, LOG_DEBUG);
snd_pcm_prepare(pcm_play_ptr);
continue;
} else if (written == -EINVAL) {
msg = "Invalid argument passed to snd_pcm_writei: ";
msg += snd_strerror(written);
log_file->write_report(msg, "t_alsa_io::write", LOG_NORMAL, LOG_DEBUG);
} else if (written < 0) {
msg = "PCM write error: ";
msg += snd_strerror(written);
log_file->write_report(msg, "t_alsa_io::write", LOG_NORMAL, LOG_DEBUG);
return -1;
} else if (written < len_frames) {
buf += written * play_framesize;
len_frames -= written;
frames_written += written;
continue;
}
return (frames_written + written) * play_framesize;
}
}
// This function fills the specified list with ALSA hardware soundcards found on the system.
// It uses plughw:xx instead of hw:xx for specifiers, because hw:xx are not practical to
// use (e.g. they require a resampler/channel mixer in the application).
// playback indicates if a list with playback or capture devices should be created.
void alsa_fill_soundcards(list<t_audio_device>& l, bool playback)
{
int err = 0;
int card = -1, device = -1;
snd_ctl_t *handle;
snd_ctl_card_info_t *info;
snd_pcm_info_t *pcminfo;
snd_ctl_card_info_alloca(&info);
snd_pcm_info_alloca(&pcminfo);
for (;;) {
err = snd_card_next(&card);
if (err < 0 || card < 0) break;
if (card >= 0) {
string name = "hw:";
name += int2str(card);
if ((err = snd_ctl_open(&handle, name.c_str(), 0)) < 0) continue;
if ((err = snd_ctl_card_info(handle, info)) < 0) {
snd_ctl_close(handle);
continue;
}
const char *card_name = snd_ctl_card_info_get_name(info);
for (;;) {
err = snd_ctl_pcm_next_device(handle, &device);
if (err < 0 || device < 0) break;
snd_pcm_info_set_device(pcminfo, device);
snd_pcm_info_set_subdevice(pcminfo, 0);
if (playback) {
snd_pcm_info_set_stream(pcminfo, SND_PCM_STREAM_PLAYBACK);
} else {
snd_pcm_info_set_stream(pcminfo, SND_PCM_STREAM_CAPTURE);
}
if ((err = snd_ctl_pcm_info(handle, pcminfo)) < 0) continue;
t_audio_device dev;
dev.device = string("plughw:") + int2str(card) +
string(",") + int2str(device);
dev.name = string(card_name) + " (";
dev.name += snd_pcm_info_get_name(pcminfo);
dev.name += ")";
dev.type = t_audio_device::ALSA;
l.push_back(dev);
}
snd_ctl_close(handle);
}
}
}
// endif ifdef HAVE_LIBASOUND
#endif
syntax highlighted by Code2HTML, v. 0.9.1