* THE SOFTWARE.
*/
#include <alsa/asoundlib.h>
-#include "vl.h"
+#include "qemu-common.h"
+#include "audio.h"
#define AUDIO_CAP "alsa"
#include "audio_int.h"
HWVoiceOut hw;
void *pcm_buf;
snd_pcm_t *handle;
- int can_pause;
- int was_enabled;
} ALSAVoiceOut;
typedef struct ALSAVoiceIn {
HWVoiceIn hw;
snd_pcm_t *handle;
void *pcm_buf;
- int can_pause;
} ALSAVoiceIn;
static struct {
unsigned int period_size_out;
unsigned int threshold;
- int buffer_size_in_overriden;
- int period_size_in_overriden;
+ int buffer_size_in_overridden;
+ int period_size_in_overridden;
- int buffer_size_out_overriden;
- int period_size_out_overriden;
+ int buffer_size_out_overridden;
+ int period_size_out_overridden;
+ int verbose;
} conf = {
+#define DEFAULT_BUFFER_SIZE 1024
+#define DEFAULT_PERIOD_SIZE 256
#ifdef HIGH_LATENCY
.size_in_usec_in = 1,
.size_in_usec_out = 1,
#endif
- .pcm_name_out = "hw:0,0",
- .pcm_name_in = "hw:0,0",
+ .pcm_name_out = "default",
+ .pcm_name_in = "default",
#ifdef HIGH_LATENCY
.buffer_size_in = 400000,
.period_size_in = 400000 / 4,
.buffer_size_out = 400000,
.period_size_out = 400000 / 4,
#else
-#define DEFAULT_BUFFER_SIZE 1024
-#define DEFAULT_PERIOD_SIZE 256
- .buffer_size_in = DEFAULT_BUFFER_SIZE,
- .period_size_in = DEFAULT_PERIOD_SIZE,
+ .buffer_size_in = DEFAULT_BUFFER_SIZE * 4,
+ .period_size_in = DEFAULT_PERIOD_SIZE * 4,
.buffer_size_out = DEFAULT_BUFFER_SIZE,
.period_size_out = DEFAULT_PERIOD_SIZE,
- .buffer_size_in_overriden = 0,
- .buffer_size_out_overriden = 0,
- .period_size_in_overriden = 0,
- .period_size_out_overriden = 0,
+ .buffer_size_in_overridden = 0,
+ .buffer_size_out_overridden = 0,
+ .period_size_in_overridden = 0,
+ .period_size_out_overridden = 0,
#endif
- .threshold = 0
+ .threshold = 0,
+ .verbose = 0
};
struct alsa_params_req {
- int freq;
+ unsigned int freq;
audfmt_e fmt;
- int nchannels;
+ unsigned int nchannels;
unsigned int buffer_size;
unsigned int period_size;
};
int freq;
audfmt_e fmt;
int nchannels;
- int can_pause;
- snd_pcm_uframes_t buffer_size;
+ snd_pcm_uframes_t samples;
};
static void GCC_FMT_ATTR (2, 3) alsa_logerr (int err, const char *fmt, ...)
{
va_list ap;
- AUD_log (AUDIO_CAP, "Can not initialize %s\n", typ);
+ AUD_log (AUDIO_CAP, "Could not initialize %s\n", typ);
va_start (ap, fmt);
AUD_vlog (AUDIO_CAP, fmt, ap);
case AUD_FMT_U16:
return SND_PCM_FORMAT_U16_LE;
+ case AUD_FMT_S32:
+ return SND_PCM_FORMAT_S32_LE;
+
+ case AUD_FMT_U32:
+ return SND_PCM_FORMAT_U32_LE;
+
default:
dolog ("Internal logic error: Bad audio format %d\n", fmt);
#ifdef DEBUG_AUDIO
*fmt = AUD_FMT_U16;
break;
+ case SND_PCM_FORMAT_S32_LE:
+ *endianness = 0;
+ *fmt = AUD_FMT_S32;
+ break;
+
+ case SND_PCM_FORMAT_U32_LE:
+ *endianness = 0;
+ *fmt = AUD_FMT_U32;
+ break;
+
+ case SND_PCM_FORMAT_S32_BE:
+ *endianness = 1;
+ *fmt = AUD_FMT_S32;
+ break;
+
+ case SND_PCM_FORMAT_U32_BE:
+ *endianness = 1;
+ *fmt = AUD_FMT_U32;
+ break;
+
default:
dolog ("Unrecognized audio format %d\n", alsafmt);
return -1;
return 0;
}
-#ifdef DEBUG_MISMATCHES
+#if defined DEBUG_MISMATCHES || defined DEBUG
static void alsa_dump_info (struct alsa_params_req *req,
struct alsa_params_obt *obt)
{
dolog ("============================================\n");
dolog ("requested: buffer size %d period size %d\n",
req->buffer_size, req->period_size);
- dolog ("obtained: buffer size %ld\n", obt->buffer_size);
+ dolog ("obtained: samples %ld\n", obt->samples);
}
#endif
err = snd_pcm_sw_params_current (handle, sw_params);
if (err < 0) {
- dolog ("Can not fully initialize DAC\n");
+ dolog ("Could not fully initialize DAC\n");
alsa_logerr (err, "Failed to get current software parameters\n");
return;
}
err = snd_pcm_sw_params_set_start_threshold (handle, sw_params, threshold);
if (err < 0) {
- dolog ("Can not fully initialize DAC\n");
+ dolog ("Could not fully initialize DAC\n");
alsa_logerr (err, "Failed to set software threshold to %ld\n",
threshold);
return;
err = snd_pcm_sw_params (handle, sw_params);
if (err < 0) {
- dolog ("Can not fully initialize DAC\n");
+ dolog ("Could not fully initialize DAC\n");
alsa_logerr (err, "Failed to set software parameters\n");
return;
}
{
snd_pcm_t *handle;
snd_pcm_hw_params_t *hw_params;
- int err, freq, nchannels;
+ int err;
+ unsigned int freq, nchannels;
const char *pcm_name = in ? conf.pcm_name_in : conf.pcm_name_out;
unsigned int period_size, buffer_size;
snd_pcm_uframes_t obt_buffer_size;
handle,
hw_params,
&period_size,
- 0);
+ 0
+ );
if (err < 0) {
alsa_logerr2 (err, typ,
"Failed to set period time %d\n",
handle,
hw_params,
&buffer_size,
- 0);
+ 0
+ );
if (err < 0) {
alsa_logerr2 (err, typ,
if (err < 0) {
alsa_logerr (
err,
- "Can not get minmal period size for %s\n",
+ "Could not get minmal period size for %s\n",
typ
);
}
else {
if (period_size < minval) {
- if ((in && conf.period_size_in_overriden)
- || (!in && conf.period_size_out_overriden)) {
+ if ((in && conf.period_size_in_overridden)
+ || (!in && conf.period_size_out_overridden)) {
dolog ("%s period size(%d) is less "
"than minmal period size(%ld)\n",
typ,
&minval
);
if (err < 0) {
- alsa_logerr (err, "Can not get minmal buffer size for %s\n",
+ alsa_logerr (err, "Could not get minmal buffer size for %s\n",
typ);
}
else {
if (buffer_size < minval) {
- if ((in && conf.buffer_size_in_overriden)
- || (!in && conf.buffer_size_out_overriden)) {
+ if ((in && conf.buffer_size_in_overridden)
+ || (!in && conf.buffer_size_out_overridden)) {
dolog (
"%s buffer size(%d) is less "
"than minimal buffer size(%ld)\n",
}
}
else {
- dolog ("warning: buffer size is not set\n");
+ dolog ("warning: Buffer size is not set\n");
}
err = snd_pcm_hw_params (handle, hw_params);
err = snd_pcm_prepare (handle);
if (err < 0) {
- alsa_logerr2 (err, typ, "Can not prepare handle %p\n", handle);
+ alsa_logerr2 (err, typ, "Could not prepare handle %p\n", handle);
goto err;
}
- obt->can_pause = snd_pcm_hw_params_can_pause (hw_params);
- if (obt->can_pause < 0) {
- alsa_logerr (err, "Can not get pause capability for %s\n", typ);
- obt->can_pause = 0;
- }
-
if (!in && conf.threshold) {
snd_pcm_uframes_t threshold;
int bytes_per_sec;
obt->fmt = req->fmt;
obt->nchannels = nchannels;
obt->freq = freq;
- obt->buffer_size = snd_pcm_frames_to_bytes (handle, obt_buffer_size);
+ obt->samples = obt_buffer_size;
*handlep = handle;
+#if defined DEBUG_MISMATCHES || defined DEBUG
if (obt->fmt != req->fmt ||
obt->nchannels != req->nchannels ||
obt->freq != req->freq) {
-#ifdef DEBUG_MISMATCHES
dolog ("Audio paramters mismatch for %s\n", typ);
alsa_dump_info (req, obt);
-#endif
}
+#endif
#ifdef DEBUG
alsa_dump_info (req, obt);
return 0;
}
+static snd_pcm_sframes_t alsa_get_avail (snd_pcm_t *handle)
+{
+ snd_pcm_sframes_t avail;
+
+ avail = snd_pcm_avail_update (handle);
+ if (avail < 0) {
+ if (avail == -EPIPE) {
+ if (!alsa_recover (handle)) {
+ avail = snd_pcm_avail_update (handle);
+ }
+ }
+
+ if (avail < 0) {
+ alsa_logerr (avail,
+ "Could not obtain number of available frames\n");
+ return -1;
+ }
+ }
+
+ return avail;
+}
+
static int alsa_run_out (HWVoiceOut *hw)
{
ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw;
return 0;
}
- avail = snd_pcm_avail_update (alsa->handle);
+ avail = alsa_get_avail (alsa->handle);
if (avail < 0) {
- if (avail == -EPIPE) {
- if (!alsa_recover (alsa->handle)) {
- avail = snd_pcm_avail_update (alsa->handle);
- if (avail >= 0) {
- goto ok;
- }
- }
- }
-
- alsa_logerr (avail, "Can not get amount free space\n");
+ dolog ("Could not get number of available playback frames\n");
return 0;
}
- ok:
decr = audio_MIN (live, avail);
samples = decr;
rpos = hw->rpos;
while (samples) {
int left_till_end_samples = hw->samples - rpos;
- int convert_samples = audio_MIN (samples, left_till_end_samples);
+ int len = audio_MIN (samples, left_till_end_samples);
snd_pcm_sframes_t written;
src = hw->mix_buf + rpos;
dst = advance (alsa->pcm_buf, rpos << hw->info.shift);
- hw->clip (dst, src, convert_samples);
+ hw->clip (dst, src, len);
- again:
- written = snd_pcm_writei (alsa->handle, dst, convert_samples);
+ while (len) {
+ written = snd_pcm_writei (alsa->handle, dst, len);
- if (written < 0) {
- switch (written) {
- case -EPIPE:
- if (!alsa_recover (alsa->handle)) {
- goto again;
- }
- dolog (
- "Failed to write %d frames to %p, handle %p not prepared\n",
- convert_samples,
- dst,
- alsa->handle
- );
- goto exit;
+ if (written <= 0) {
+ switch (written) {
+ case 0:
+ if (conf.verbose) {
+ dolog ("Failed to write %d frames (wrote zero)\n", len);
+ }
+ goto exit;
+
+ case -EPIPE:
+ if (alsa_recover (alsa->handle)) {
+ alsa_logerr (written, "Failed to write %d frames\n",
+ len);
+ goto exit;
+ }
+ if (conf.verbose) {
+ dolog ("Recovering from playback xrun\n");
+ }
+ continue;
- case -EAGAIN:
- goto again;
+ case -EAGAIN:
+ goto exit;
- default:
- alsa_logerr (written, "Failed to write %d frames to %p\n",
- convert_samples, dst);
- goto exit;
+ default:
+ alsa_logerr (written, "Failed to write %d frames to %p\n",
+ len, dst);
+ goto exit;
+ }
}
- }
- mixeng_clear (src, written);
- rpos = (rpos + written) % hw->samples;
- samples -= written;
+ rpos = (rpos + written) % hw->samples;
+ samples -= written;
+ len -= written;
+ dst = advance (dst, written << hw->info.shift);
+ src += written;
+ }
}
exit:
}
}
-static int alsa_init_out (HWVoiceOut *hw, int freq, int nchannels, audfmt_e fmt)
+static int alsa_init_out (HWVoiceOut *hw, audsettings_t *as)
{
ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw;
struct alsa_params_req req;
int endianness;
int err;
snd_pcm_t *handle;
+ audsettings_t obt_as;
- req.fmt = aud_to_alsafmt (fmt);
- req.freq = freq;
- req.nchannels = nchannels;
+ req.fmt = aud_to_alsafmt (as->fmt);
+ req.freq = as->freq;
+ req.nchannels = as->nchannels;
req.period_size = conf.period_size_out;
req.buffer_size = conf.buffer_size_out;
return -1;
}
- audio_pcm_init_info (
- &hw->info,
- obt.freq,
- obt.nchannels,
- effective_fmt,
- audio_need_to_swap_endian (endianness)
- );
- alsa->can_pause = obt.can_pause;
- hw->bufsize = obt.buffer_size;
+ obt_as.freq = obt.freq;
+ obt_as.nchannels = obt.nchannels;
+ obt_as.fmt = effective_fmt;
+ obt_as.endianness = endianness;
- alsa->pcm_buf = qemu_mallocz (hw->bufsize);
+ audio_pcm_init_info (&hw->info, &obt_as);
+ hw->samples = obt.samples;
+
+ alsa->pcm_buf = audio_calloc (AUDIO_FUNC, obt.samples, 1 << hw->info.shift);
if (!alsa->pcm_buf) {
+ dolog ("Could not allocate DAC buffer (%d samples, each %d bytes)\n",
+ hw->samples, 1 << hw->info.shift);
alsa_anal_close (&handle);
return -1;
}
alsa->handle = handle;
- alsa->was_enabled = 0;
return 0;
}
-static int alsa_ctl_out (HWVoiceOut *hw, int cmd, ...)
+static int alsa_voice_ctl (snd_pcm_t *handle, const char *typ, int pause)
{
int err;
+
+ if (pause) {
+ err = snd_pcm_drop (handle);
+ if (err < 0) {
+ alsa_logerr (err, "Could not stop %s\n", typ);
+ return -1;
+ }
+ }
+ else {
+ err = snd_pcm_prepare (handle);
+ if (err < 0) {
+ alsa_logerr (err, "Could not prepare handle for %s\n", typ);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int alsa_ctl_out (HWVoiceOut *hw, int cmd, ...)
+{
ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw;
switch (cmd) {
case VOICE_ENABLE:
ldebug ("enabling voice\n");
- audio_pcm_info_clear_buf (&hw->info, alsa->pcm_buf, hw->samples);
- if (alsa->can_pause) {
- /* Why this was_enabled madness is needed at all?? */
- if (alsa->was_enabled) {
- err = snd_pcm_pause (alsa->handle, 0);
- if (err < 0) {
- alsa_logerr (err, "Failed to resume playing\n");
- /* not fatal really */
- }
- }
- else {
- alsa->was_enabled = 1;
- }
- }
- break;
+ return alsa_voice_ctl (alsa->handle, "playback", 0);
case VOICE_DISABLE:
ldebug ("disabling voice\n");
- if (alsa->can_pause) {
- err = snd_pcm_pause (alsa->handle, 1);
- if (err < 0) {
- alsa_logerr (err, "Failed to stop playing\n");
- /* not fatal really */
- }
- }
- break;
+ return alsa_voice_ctl (alsa->handle, "playback", 1);
}
- return 0;
+
+ return -1;
}
-static int alsa_init_in (HWVoiceIn *hw,
- int freq, int nchannels, audfmt_e fmt)
+static int alsa_init_in (HWVoiceIn *hw, audsettings_t *as)
{
ALSAVoiceIn *alsa = (ALSAVoiceIn *) hw;
struct alsa_params_req req;
int err;
audfmt_e effective_fmt;
snd_pcm_t *handle;
+ audsettings_t obt_as;
- req.fmt = aud_to_alsafmt (fmt);
- req.freq = freq;
- req.nchannels = nchannels;
+ req.fmt = aud_to_alsafmt (as->fmt);
+ req.freq = as->freq;
+ req.nchannels = as->nchannels;
req.period_size = conf.period_size_in;
req.buffer_size = conf.buffer_size_in;
return -1;
}
- audio_pcm_init_info (
- &hw->info,
- obt.freq,
- obt.nchannels,
- effective_fmt,
- audio_need_to_swap_endian (endianness)
- );
- alsa->can_pause = obt.can_pause;
- hw->bufsize = obt.buffer_size;
- alsa->pcm_buf = qemu_mallocz (hw->bufsize);
+ obt_as.freq = obt.freq;
+ obt_as.nchannels = obt.nchannels;
+ obt_as.fmt = effective_fmt;
+ obt_as.endianness = endianness;
+
+ audio_pcm_init_info (&hw->info, &obt_as);
+ hw->samples = obt.samples;
+
+ alsa->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift);
if (!alsa->pcm_buf) {
+ dolog ("Could not allocate ADC buffer (%d samples, each %d bytes)\n",
+ hw->samples, 1 << hw->info.shift);
alsa_anal_close (&handle);
return -1;
}
int i;
int live = audio_pcm_hw_get_live_in (hw);
int dead = hw->samples - live;
+ int decr;
struct {
int add;
int len;
{ hw->wpos, 0 },
{ 0, 0 }
};
-
+ snd_pcm_sframes_t avail;
snd_pcm_uframes_t read_samples = 0;
if (!dead) {
return 0;
}
- if (hw->wpos + dead > hw->samples) {
+ avail = alsa_get_avail (alsa->handle);
+ if (avail < 0) {
+ dolog ("Could not get number of captured frames\n");
+ return 0;
+ }
+
+ if (!avail && (snd_pcm_state (alsa->handle) == SND_PCM_STATE_PREPARED)) {
+ avail = hw->samples;
+ }
+
+ decr = audio_MIN (dead, avail);
+ if (!decr) {
+ return 0;
+ }
+
+ if (hw->wpos + decr > hw->samples) {
bufs[0].len = (hw->samples - hw->wpos);
- bufs[1].len = (dead - (hw->samples - hw->wpos));
+ bufs[1].len = (decr - (hw->samples - hw->wpos));
}
else {
- bufs[0].len = dead;
+ bufs[0].len = decr;
}
-
for (i = 0; i < 2; ++i) {
void *src;
st_sample_t *dst;
while (len) {
nread = snd_pcm_readi (alsa->handle, src, len);
- if (nread < 0) {
+ if (nread <= 0) {
switch (nread) {
- case -EPIPE:
- if (!alsa_recover (alsa->handle)) {
- continue;
+ case 0:
+ if (conf.verbose) {
+ dolog ("Failed to read %ld frames (read zero)\n", len);
}
- dolog (
- "Failed to read %ld frames from %p, "
- "handle %p not prepared\n",
- len,
- src,
- alsa->handle
- );
goto exit;
- case -EAGAIN:
+ case -EPIPE:
+ if (alsa_recover (alsa->handle)) {
+ alsa_logerr (nread, "Failed to read %ld frames\n", len);
+ goto exit;
+ }
+ if (conf.verbose) {
+ dolog ("Recovering from capture xrun\n");
+ }
continue;
+ case -EAGAIN:
+ goto exit;
+
default:
alsa_logerr (
nread,
static int alsa_ctl_in (HWVoiceIn *hw, int cmd, ...)
{
- (void) hw;
- (void) cmd;
- return 0;
+ ALSAVoiceIn *alsa = (ALSAVoiceIn *) hw;
+
+ switch (cmd) {
+ case VOICE_ENABLE:
+ ldebug ("enabling voice\n");
+ return alsa_voice_ctl (alsa->handle, "capture", 0);
+
+ case VOICE_DISABLE:
+ ldebug ("disabling voice\n");
+ return alsa_voice_ctl (alsa->handle, "capture", 1);
+ }
+
+ return -1;
}
static void *alsa_audio_init (void)
{"DAC_SIZE_IN_USEC", AUD_OPT_BOOL, &conf.size_in_usec_out,
"DAC period/buffer size in microseconds (otherwise in frames)", NULL, 0},
{"DAC_PERIOD_SIZE", AUD_OPT_INT, &conf.period_size_out,
- "DAC period size", &conf.period_size_out_overriden, 0},
+ "DAC period size", &conf.period_size_out_overridden, 0},
{"DAC_BUFFER_SIZE", AUD_OPT_INT, &conf.buffer_size_out,
- "DAC buffer size", &conf.buffer_size_out_overriden, 0},
+ "DAC buffer size", &conf.buffer_size_out_overridden, 0},
{"ADC_SIZE_IN_USEC", AUD_OPT_BOOL, &conf.size_in_usec_in,
"ADC period/buffer size in microseconds (otherwise in frames)", NULL, 0},
{"ADC_PERIOD_SIZE", AUD_OPT_INT, &conf.period_size_in,
- "ADC period size", &conf.period_size_in_overriden, 0},
+ "ADC period size", &conf.period_size_in_overridden, 0},
{"ADC_BUFFER_SIZE", AUD_OPT_INT, &conf.buffer_size_in,
- "ADC buffer size", &conf.buffer_size_in_overriden, 0},
+ "ADC buffer size", &conf.buffer_size_in_overridden, 0},
{"THRESHOLD", AUD_OPT_INT, &conf.threshold,
"(undocumented)", NULL, 0},
{"ADC_DEV", AUD_OPT_STR, &conf.pcm_name_in,
"ADC device name", NULL, 0},
+
+ {"VERBOSE", AUD_OPT_BOOL, &conf.verbose,
+ "Behave in a more verbose way", NULL, 0},
+
{NULL, 0, NULL, NULL, NULL, 0}
};